From 7af4177e988b25961f2b40df6e8bfc59f9b0e304 Mon Sep 17 00:00:00 2001 From: Maciej Rapacz Date: Sat, 21 Apr 2018 17:17:30 +0200 Subject: [PATCH] refactor(elm): Separate models (#218) --- aion/web/elm/src/App.elm | 14 +- aion/web/elm/src/Auth/Constants.elm | 19 +++ aion/web/elm/src/Auth/Models.elm | 27 ++- aion/web/elm/src/Auth/Notifications.elm | 8 +- aion/web/elm/src/Auth/Update.elm | 109 +++--------- aion/web/elm/src/Auth/View.elm | 13 +- aion/web/elm/src/Constants.elm | 9 + aion/web/elm/src/General/Api.elm | 15 -- aion/web/elm/src/General/Constants.elm | 52 ------ aion/web/elm/src/General/Models.elm | 124 -------------- aion/web/elm/src/General/Msgs.elm | 8 - aion/web/elm/src/General/Update.elm | 11 -- aion/web/elm/src/Lobby/Api.elm | 46 +++++ aion/web/elm/src/Lobby/Decoders.elm | 20 +++ aion/web/elm/src/Lobby/Models.elm | 36 ++++ aion/web/elm/src/Lobby/Msgs.elm | 8 + aion/web/elm/src/Lobby/Update.elm | 21 +++ aion/web/elm/src/{General => Lobby}/Utils.elm | 24 ++- aion/web/elm/src/{General => Lobby}/View.elm | 34 ++-- aion/web/elm/src/Models.elm | 82 +++++++++ aion/web/elm/src/Msgs.elm | 18 +- aion/web/elm/src/Panel/Api.elm | 13 +- aion/web/elm/src/Panel/Models.elm | 26 ++- aion/web/elm/src/Panel/Notifications.elm | 23 ++- aion/web/elm/src/Panel/Update.elm | 160 ++++-------------- aion/web/elm/src/Panel/View.elm | 7 +- aion/web/elm/src/Ranking/Models.elm | 10 ++ aion/web/elm/src/Ranking/Update.elm | 14 +- aion/web/elm/src/Ranking/View.elm | 18 +- aion/web/elm/src/Room/Api.elm | 36 ---- aion/web/elm/src/Room/Decoders.elm | 18 +- aion/web/elm/src/Room/Models.elm | 90 +++++++--- aion/web/elm/src/Room/Notifications.elm | 10 +- aion/web/elm/src/{ => Room}/Socket.elm | 26 +-- aion/web/elm/src/Room/Subscriptions.elm | 5 +- aion/web/elm/src/Room/Update.elm | 32 ++-- aion/web/elm/src/Room/Utils.elm | 30 +--- aion/web/elm/src/Room/View.elm | 20 +-- aion/web/elm/src/Routing.elm | 4 +- aion/web/elm/src/Update.elm | 66 ++++---- aion/web/elm/src/UpdateHelpers.elm | 14 +- aion/web/elm/src/Urls.elm | 59 +++++-- aion/web/elm/src/User/Models.elm | 21 +++ aion/web/elm/src/User/Update.elm | 23 +-- aion/web/elm/src/User/View.elm | 21 ++- aion/web/elm/src/View.elm | 26 +-- 46 files changed, 693 insertions(+), 777 deletions(-) create mode 100644 aion/web/elm/src/Auth/Constants.elm create mode 100644 aion/web/elm/src/Constants.elm delete mode 100644 aion/web/elm/src/General/Api.elm delete mode 100644 aion/web/elm/src/General/Constants.elm delete mode 100644 aion/web/elm/src/General/Models.elm delete mode 100644 aion/web/elm/src/General/Msgs.elm delete mode 100644 aion/web/elm/src/General/Update.elm create mode 100644 aion/web/elm/src/Lobby/Api.elm create mode 100644 aion/web/elm/src/Lobby/Decoders.elm create mode 100644 aion/web/elm/src/Lobby/Models.elm create mode 100644 aion/web/elm/src/Lobby/Msgs.elm create mode 100644 aion/web/elm/src/Lobby/Update.elm rename aion/web/elm/src/{General => Lobby}/Utils.elm (52%) rename aion/web/elm/src/{General => Lobby}/View.elm (69%) create mode 100644 aion/web/elm/src/Models.elm delete mode 100644 aion/web/elm/src/Room/Api.elm rename aion/web/elm/src/{ => Room}/Socket.elm (78%) diff --git a/aion/web/elm/src/App.elm b/aion/web/elm/src/App.elm index e231505..a6f993b 100644 --- a/aion/web/elm/src/App.elm +++ b/aion/web/elm/src/App.elm @@ -1,14 +1,14 @@ module App exposing (..) import Bootstrap.Navbar as Navbar -import General.Models exposing (Flags, Model, initialModel) -import Msgs exposing (Msg(MkGeneralMsg, MkPanelMsg, MkRoomMsg, MkUserMsg, NavbarMsg)) +import Lobby.Api exposing (fetchRooms) +import Models exposing (Flags, Model, initModel) +import Msgs exposing (Msg(MkLobbyMsg, MkPanelMsg, MkRoomMsg, MkUserMsg, NavbarMsg)) import Multiselect import Navigation exposing (Location, modifyUrl) import Panel.Api exposing (fetchCategories) import Panel.Msgs exposing (PanelMsg(MultiselectMsg)) import Phoenix.Socket -import Room.Api exposing (fetchRooms) import Room.Msgs exposing (RoomMsg(PhoenixMsg)) import Room.Subscriptions import Routing @@ -28,13 +28,13 @@ init flags location = Navbar.initialState NavbarMsg getInitialModel = - initialModel flags currentRoute location + initModel flags currentRoute location in ( { getInitialModel | navbarState = navbarState } , Cmd.batch [ setHomeUrl location , navbarCmd - , Cmd.map MkGeneralMsg (fetchRooms location flags.token) + , Cmd.map MkLobbyMsg (fetchRooms location flags.token) , Cmd.map MkPanelMsg (fetchCategories location flags.token) , Cmd.map MkUserMsg (fetchCurrentUser location flags.token) ] @@ -44,10 +44,10 @@ init flags location = subscriptions : Model -> Sub Msg subscriptions model = Sub.batch - [ Phoenix.Socket.listen model.socket PhoenixMsg |> Sub.map MkRoomMsg + [ Phoenix.Socket.listen model.roomData.socket PhoenixMsg |> Sub.map MkRoomMsg , Navbar.subscriptions model.navbarState NavbarMsg , Multiselect.subscriptions model.panelData.categoryMultiSelect |> Sub.map MultiselectMsg |> Sub.map MkPanelMsg - , Room.Subscriptions.subscriptions model |> Sub.map MkRoomMsg + , Room.Subscriptions.subscriptions model.roomData |> Sub.map MkRoomMsg ] diff --git a/aion/web/elm/src/Auth/Constants.elm b/aion/web/elm/src/Auth/Constants.elm new file mode 100644 index 0000000..4b17d0f --- /dev/null +++ b/aion/web/elm/src/Auth/Constants.elm @@ -0,0 +1,19 @@ +module Auth.Constants exposing (..) + + +loginFormMsg : String +loginFormMsg = + "Don't have an account? Click here to register." + + +registerFormMsg : String +registerFormMsg = + "Already have an account? Click here to login." + + +authPageRightColumnContent : List String +authPageRightColumnContent = + [ "Aion is an e-learning platform written in Elixir and Elm based on real-time gameplay." + , "It's basically an erudite quiz with over 4000 questions." + , "Challenge your friends, gather points and climb up the rankings." + ] diff --git a/aion/web/elm/src/Auth/Models.elm b/aion/web/elm/src/Auth/Models.elm index 0225672..58dc15a 100644 --- a/aion/web/elm/src/Auth/Models.elm +++ b/aion/web/elm/src/Auth/Models.elm @@ -1,15 +1,34 @@ module Auth.Models exposing (..) +import Auth.Constants exposing (loginFormMsg) import Forms +import Navigation exposing (Location) +import Toasty +import Toasty.Defaults type alias AuthData = - { loginForm : LoginForm + { formMsg : String + , location : Location + , loginForm : LoginForm + , msg : String , registrationForm : RegistrationForm - , unauthenticatedView : UnauthenticatedViewToggle - , formMsg : String + , toasties : Toasty.Stack Toasty.Defaults.Toast , token : Maybe Token - , msg : String + , unauthenticatedView : UnauthenticatedViewToggle + } + + +initAuthData : Location -> Maybe Token -> AuthData +initAuthData location token = + { formMsg = loginFormMsg + , location = location + , loginForm = Forms.initForm loginForm + , msg = "" + , registrationForm = Forms.initForm registrationForm + , token = token + , toasties = Toasty.initialState + , unauthenticatedView = LoginView } diff --git a/aion/web/elm/src/Auth/Notifications.elm b/aion/web/elm/src/Auth/Notifications.elm index 3bd3458..d42f242 100644 --- a/aion/web/elm/src/Auth/Notifications.elm +++ b/aion/web/elm/src/Auth/Notifications.elm @@ -1,7 +1,7 @@ module Auth.Notifications exposing (..) +import Auth.Models exposing (AuthData) import Auth.Msgs exposing (AuthMsg(ToastyMsg)) -import General.Models exposing (Model) import Html.Attributes exposing (class) import Toasty import Toasty.Defaults @@ -10,7 +10,7 @@ import Toasty.Defaults -- registration notifications -registrationErrorToast : ( Model, Cmd AuthMsg ) -> ( Model, Cmd AuthMsg ) +registrationErrorToast : ( AuthData, Cmd AuthMsg ) -> ( AuthData, Cmd AuthMsg ) registrationErrorToast = addToast (Toasty.Defaults.Error "Error!" "Failed to register :(") @@ -19,7 +19,7 @@ registrationErrorToast = -- login notifications -loginErrorToast : ( Model, Cmd AuthMsg ) -> ( Model, Cmd AuthMsg ) +loginErrorToast : ( AuthData, Cmd AuthMsg ) -> ( AuthData, Cmd AuthMsg ) loginErrorToast = addToast (Toasty.Defaults.Error "Error!" "Failed to login :(") @@ -31,6 +31,6 @@ toastsConfig = |> Toasty.containerAttrs [ class "toasty-notification" ] -addToast : Toasty.Defaults.Toast -> ( Model, Cmd AuthMsg ) -> ( Model, Cmd AuthMsg ) +addToast : Toasty.Defaults.Toast -> ( AuthData, Cmd AuthMsg ) -> ( AuthData, Cmd AuthMsg ) addToast toast ( model, cmd ) = Toasty.addToast toastsConfig ToastyMsg toast ( model, cmd ) diff --git a/aion/web/elm/src/Auth/Update.elm b/aion/web/elm/src/Auth/Update.elm index b5db6b2..5695dba 100644 --- a/aion/web/elm/src/Auth/Update.elm +++ b/aion/web/elm/src/Auth/Update.elm @@ -1,45 +1,33 @@ module Auth.Update exposing (..) import Auth.Api exposing (registerUser, submitCredentials) -import Auth.Models exposing (UnauthenticatedViewToggle(LoginView, RegisterView)) +import Auth.Constants exposing (loginFormMsg, registerFormMsg) +import Auth.Models exposing (AuthData, UnauthenticatedViewToggle(LoginView, RegisterView)) import Auth.Msgs exposing (AuthMsg(ChangeAuthForm, Login, LoginResult, Register, RegistrationResult, ToastyMsg, UpdateLoginForm, UpdateRegistrationForm)) import Auth.Notifications exposing (loginErrorToast, registrationErrorToast, toastsConfig) import Forms -import General.Constants exposing (loginFormMsg, registerFormMsg) -import General.Models exposing (Model) import RemoteData -import Socket exposing (initSocket) import Toasty import UpdateHelpers exposing (postTokenActions, updateForm) -update : AuthMsg -> Model -> ( Model, Cmd AuthMsg ) +update : AuthMsg -> AuthData -> ( AuthData, Cmd AuthMsg ) update msg model = case msg of Login -> model - ! [ submitCredentials model.location model.authData.loginForm ] + ! [ submitCredentials model.location model.loginForm ] LoginResult res -> - let - oldAuthData = - model.authData - in - case res of - Ok token -> - { model - | authData = { oldAuthData | token = Just token, msg = "" } - , socket = initSocket token model.location - } - ! [] + case res of + Ok token -> + { model | token = Just token, msg = "" } ! [] - Err err -> - { model | authData = { oldAuthData | msg = toString err } } - ! [] - |> loginErrorToast + Err err -> + { model | msg = toString err } ! [] |> loginErrorToast Register -> - model ! [ registerUser model.location model.authData.registrationForm ] + model ! [ registerUser model.location model.registrationForm ] RegistrationResult response -> case response of @@ -48,90 +36,37 @@ update msg model = token = responseData.token - oldAuthData = - model.authData - - oldRegistrationForm = - oldAuthData.registrationForm - newRegistrationForm = - updateForm "name" "" oldRegistrationForm + updateForm "name" "" model.registrationForm |> updateForm "email" "" |> updateForm "password" "" in - { model - | authData = { oldAuthData | registrationForm = newRegistrationForm, token = Just token } - , socket = initSocket token model.location - } - ! [] + { model | registrationForm = newRegistrationForm, token = Just token } ! [] _ -> - model - ! [] - |> registrationErrorToast + model ! [] |> registrationErrorToast ChangeAuthForm -> - let - oldAuthData = - model.authData + case model.unauthenticatedView of + LoginView -> + { model | unauthenticatedView = RegisterView, formMsg = registerFormMsg } ! [] - oldUnauthenticatedView = - oldAuthData.unauthenticatedView - in - case oldUnauthenticatedView of - LoginView -> - { model - | authData = - { oldAuthData - | unauthenticatedView = RegisterView - , formMsg = registerFormMsg - } - } - ! [] - - RegisterView -> - { model - | authData = - { oldAuthData - | unauthenticatedView = LoginView - , formMsg = loginFormMsg - } - } - ! [] + RegisterView -> + { model | unauthenticatedView = LoginView, formMsg = loginFormMsg } ! [] UpdateLoginForm name value -> let - oldAuthData = - model.authData - - loginForm = - oldAuthData.loginForm - updatedLoginForm = - Forms.updateFormInput loginForm name value + Forms.updateFormInput model.loginForm name value in - { model - | authData = - { oldAuthData | loginForm = updatedLoginForm } - } - ! [] + { model | loginForm = updatedLoginForm } ! [] UpdateRegistrationForm name value -> let - oldAuthData = - model.authData - - registrationForm = - oldAuthData.registrationForm - updatedRegistrationForm = - Forms.updateFormInput registrationForm name value + Forms.updateFormInput model.registrationForm name value in - { model - | authData = - { oldAuthData | registrationForm = updatedRegistrationForm } - } - ! [] + { model | registrationForm = updatedRegistrationForm } ! [] -- Toasty ToastyMsg subMsg -> diff --git a/aion/web/elm/src/Auth/View.elm b/aion/web/elm/src/Auth/View.elm index 7f8cb30..f3028de 100644 --- a/aion/web/elm/src/Auth/View.elm +++ b/aion/web/elm/src/Auth/View.elm @@ -1,5 +1,6 @@ module Auth.View exposing (..) +import Auth.Constants exposing (authPageRightColumnContent) import Auth.Models exposing (AuthData, LoginForm, RegistrationForm, UnauthenticatedViewToggle(LoginView, RegisterView)) import Auth.Msgs exposing (AuthMsg(ChangeAuthForm, Login, Register, ToastyMsg, UpdateLoginForm, UpdateRegistrationForm)) import Auth.Notifications exposing (toastsConfig) @@ -9,28 +10,26 @@ import Bootstrap.Form as Form import Bootstrap.Form.Input as Input import Bootstrap.Grid as Grid import Forms -import General.Constants exposing (authPageRightColumnContent) -import General.Models exposing (Model) import Html exposing (Html, br, div, h2, p, span, text) import Html.Attributes exposing (class, for) import Toasty import Toasty.Defaults -authView : Model -> Html AuthMsg +authView : AuthData -> Html AuthMsg authView model = let unauthenticatedView = - model.authData.unauthenticatedView + model.unauthenticatedView loginForm = - model.authData.loginForm + model.loginForm registrationForm = - model.authData.registrationForm + model.registrationForm formMsg = - model.authData.formMsg + model.formMsg in Grid.container [ class "auth-container" ] [ Grid.container [] diff --git a/aion/web/elm/src/Constants.elm b/aion/web/elm/src/Constants.elm new file mode 100644 index 0000000..636c1d1 --- /dev/null +++ b/aion/web/elm/src/Constants.elm @@ -0,0 +1,9 @@ +module Constants exposing (..) + + +footerContent : List String +footerContent = + [ "JPKS jest projektem niekomercyjnym, stworzonym ku nauce oraz uciesze uczniów i wszystkich osób lubiących tego typu rozrywki." + , "Autorem oryginalnego pomysłu (PKS = Projekt-Klient-Serwer, napisanego w ANSI C) jest Marek Lipert, zaś cały projekt był zgłoszony w programie Diversity firmy Motorola." + , "Jeśli właściciel praw autorskich do jakiegoś materiału nie wyraża zgody na jego wykorzystanie, prosimy o kontakt z autorem quizu (Andrzej Dyrek na fb)." + ] diff --git a/aion/web/elm/src/General/Api.elm b/aion/web/elm/src/General/Api.elm deleted file mode 100644 index 68f21fb..0000000 --- a/aion/web/elm/src/General/Api.elm +++ /dev/null @@ -1,15 +0,0 @@ -module General.Api exposing (..) - -import General.Msgs exposing (GeneralMsg(OnFetchRooms)) -import Http exposing (Error(BadStatus)) -import RemoteData - - -unauthorized : GeneralMsg -> Bool -unauthorized msg = - case msg of - OnFetchRooms (RemoteData.Failure (BadStatus response)) -> - response.status.code == 401 - - _ -> - False diff --git a/aion/web/elm/src/General/Constants.elm b/aion/web/elm/src/General/Constants.elm deleted file mode 100644 index 9803b86..0000000 --- a/aion/web/elm/src/General/Constants.elm +++ /dev/null @@ -1,52 +0,0 @@ -module General.Constants exposing (..) - - -loginFormMsg : String -loginFormMsg = - "Don't have an account? Click here to register." - - -registerFormMsg : String -registerFormMsg = - "Already have an account? Click here to login." - - -roomsPath : String -roomsPath = - "#" - - -createRoomPath : String -createRoomPath = - "#create_room" - - -panelPath : String -panelPath = - "#panel" - - -userPath : String -userPath = - "#profile" - - -rankingsPath : String -rankingsPath = - "#rankings" - - -footerContent : List String -footerContent = - [ "JPKS jest projektem niekomercyjnym, stworzonym ku nauce oraz uciesze uczniów i wszystkich osób lubiących tego typu rozrywki." - , "Autorem oryginalnego pomysłu (PKS = Projekt-Klient-Serwer, napisanego w ANSI C) jest Marek Lipert, zaś cały projekt był zgłoszony w programie Diversity firmy Motorola." - , "Jeśli właściciel praw autorskich do jakiegoś materiału nie wyraża zgody na jego wykorzystanie, prosimy o kontakt z autorem quizu (Andrzej Dyrek na fb)." - ] - - -authPageRightColumnContent : List String -authPageRightColumnContent = - [ "Aion is an e-learning platform written in Elixir and Elm based on real-time gameplay." - , "It's basically an erudite quiz with over 4000 questions." - , "Challenge your friends, gather points and climb up the rankings." - ] diff --git a/aion/web/elm/src/General/Models.elm b/aion/web/elm/src/General/Models.elm deleted file mode 100644 index 2827c54..0000000 --- a/aion/web/elm/src/General/Models.elm +++ /dev/null @@ -1,124 +0,0 @@ -module General.Models exposing (..) - -import Auth.Models exposing (AuthData, Token, UnauthenticatedViewToggle(LoginView), loginForm, registrationForm) -import Bootstrap.Navbar as Navbar -import Forms -import General.Constants exposing (loginFormMsg) -import Msgs exposing (Msg(NavbarMsg)) -import Navigation exposing (Location) -import Multiselect -import Panel.Models exposing (CategoriesData, PanelData, categoryForm, questionForm, roomForm) -import Phoenix.Socket -import RemoteData exposing (WebData) -import Room.Models exposing (CurrentQuestion, EventLog, ProgressBar, RoomId, RoomState(QuestionBreak), RoomsData, UserGameData, UserList, initialLog, initialProgressBar) -import Ranking.Models exposing (RankingData) -import Room.Msgs exposing (RoomMsg) -import Toasty -import Toasty.Defaults -import Urls exposing (hostname, websocketUrl) -import User.Models exposing (UserData) - - -type alias Model = - { user : UserData - , authData : AuthData - , rooms : WebData RoomsData - , categories : WebData CategoriesData - , route : Route - , socket : Phoenix.Socket.Socket RoomMsg - , userList : UserList - , userGameData : UserGameData - , currentQuestion : CurrentQuestion - , roomId : RoomId - , toasties : Toasty.Stack Toasty.Defaults.Toast - , panelData : PanelData - , rankingData : RankingData - , navbarState : Navbar.State - , location : Location - , roomState : RoomState - , eventLog : EventLog - , progressBar : ProgressBar - } - - -type alias Flags = - { token : Token - } - - -type Route - = AuthRoute - | RoomListRoute - | RoomRoute RoomId - | CreateRoomRoute - | RankingRoute - | UserRoute - | NotFoundRoute - - -initialModel : Flags -> Route -> Location -> Model -initialModel flags route location = - let - ( navbarState, _ ) = - Navbar.initialState NavbarMsg - - token = - case String.isEmpty flags.token of - True -> - Nothing - - False -> - Just flags.token - in - { user = - { details = RemoteData.Loading - , scores = RemoteData.Loading - } - , authData = - { loginForm = Forms.initForm loginForm - , registrationForm = Forms.initForm registrationForm - , unauthenticatedView = LoginView - , formMsg = loginFormMsg - , token = token - , msg = "" - } - , rooms = RemoteData.Loading - , categories = RemoteData.Loading - , route = route - , socket = - Phoenix.Socket.init (websocketUrl location flags.token) - |> Phoenix.Socket.withDebug - , userList = [] - , userGameData = { currentAnswer = "" } - , currentQuestion = - { content = "" - , image_name = "" - } - , roomId = 0 - , toasties = Toasty.initialState - , panelData = - { questionForm = Forms.initForm questionForm - , categoryForm = Forms.initForm categoryForm - , roomForm = Forms.initForm roomForm - , categoryMultiSelect = Multiselect.initModel [] "id" - } - , rankingData = - { data = RemoteData.Loading - , selectedCategoryId = -1 - } - , navbarState = navbarState - , location = location - , roomState = QuestionBreak - , eventLog = initialLog - , progressBar = initialProgressBar - } - - -asEventLogIn : Model -> EventLog -> Model -asEventLogIn model eventLog = - { model | eventLog = eventLog } - - -asProgressBarIn : Model -> ProgressBar -> Model -asProgressBarIn model bar = - { model | progressBar = bar } diff --git a/aion/web/elm/src/General/Msgs.elm b/aion/web/elm/src/General/Msgs.elm deleted file mode 100644 index 4d1c35b..0000000 --- a/aion/web/elm/src/General/Msgs.elm +++ /dev/null @@ -1,8 +0,0 @@ -module General.Msgs exposing (..) - -import RemoteData exposing (WebData) -import Room.Models exposing (RoomsData) - - -type GeneralMsg - = OnFetchRooms (WebData RoomsData) diff --git a/aion/web/elm/src/General/Update.elm b/aion/web/elm/src/General/Update.elm deleted file mode 100644 index a33cad8..0000000 --- a/aion/web/elm/src/General/Update.elm +++ /dev/null @@ -1,11 +0,0 @@ -module General.Update exposing (..) - -import General.Models exposing (Model) -import General.Msgs exposing (GeneralMsg(OnFetchRooms)) - - -update : GeneralMsg -> Model -> ( Model, Cmd GeneralMsg ) -update msg model = - case msg of - OnFetchRooms response -> - { model | rooms = response } ! [] diff --git a/aion/web/elm/src/Lobby/Api.elm b/aion/web/elm/src/Lobby/Api.elm new file mode 100644 index 0000000..a32c65a --- /dev/null +++ b/aion/web/elm/src/Lobby/Api.elm @@ -0,0 +1,46 @@ +module Lobby.Api exposing (..) + +import Auth.Models exposing (Token) +import Http exposing (Error(BadStatus), Request) +import Lobby.Decoders exposing (roomListDecoder) +import Lobby.Msgs exposing (LobbyMsg(OnFetchRooms)) +import Navigation exposing (Location) +import RemoteData +import Urls exposing (roomsUrl) +import User.Api exposing (fetchCurrentUserRequest) +import Json.Decode as Decode +import Lobby.Models exposing (LobbyData) + + +unauthorized : LobbyMsg -> Bool +unauthorized msg = + case msg of + OnFetchRooms (RemoteData.Failure (BadStatus response)) -> + response.status.code == 401 + + _ -> + False + + +fetchRooms : Location -> Token -> Cmd LobbyMsg +fetchRooms location token = + let + url = + roomsUrl location + in + fetchRoomsRequest url token roomListDecoder + |> RemoteData.sendRequest + |> Cmd.map OnFetchRooms + + +fetchRoomsRequest : String -> String -> Decode.Decoder LobbyData -> Request LobbyData +fetchRoomsRequest url token decoder = + Http.request + { method = "GET" + , headers = [ Http.header "Authorization" ("Bearer " ++ token) ] + , url = url + , body = Http.emptyBody + , expect = Http.expectJson decoder + , timeout = Nothing + , withCredentials = True + } diff --git a/aion/web/elm/src/Lobby/Decoders.elm b/aion/web/elm/src/Lobby/Decoders.elm new file mode 100644 index 0000000..55143fc --- /dev/null +++ b/aion/web/elm/src/Lobby/Decoders.elm @@ -0,0 +1,20 @@ +module Lobby.Decoders exposing (..) + +import Json.Decode as Decode +import Json.Decode.Pipeline exposing (decode, required) +import Lobby.Models exposing (LobbyData, Room) + + +roomListDecoder : Decode.Decoder LobbyData +roomListDecoder = + decode LobbyData + |> required "data" (Decode.list (roomDecoder)) + + +roomDecoder : Decode.Decoder Room +roomDecoder = + decode Room + |> required "id" Decode.int + |> required "name" Decode.string + |> required "description" Decode.string + |> required "player_count" Decode.int diff --git a/aion/web/elm/src/Lobby/Models.elm b/aion/web/elm/src/Lobby/Models.elm new file mode 100644 index 0000000..928b375 --- /dev/null +++ b/aion/web/elm/src/Lobby/Models.elm @@ -0,0 +1,36 @@ +module Lobby.Models exposing (..) + +import RemoteData + + +type alias LobbyData = + { data : List Room } + + + +-- experimental + + +type alias RoomList = + List Room + + + +-- /experimental + + +type alias Room = + { id : RoomId + , name : String + , description : String + , player_count : Int + } + + +type alias RoomId = + Int + + +initLobbyData : LobbyData +initLobbyData = + { data = [] } diff --git a/aion/web/elm/src/Lobby/Msgs.elm b/aion/web/elm/src/Lobby/Msgs.elm new file mode 100644 index 0000000..c9ad2d7 --- /dev/null +++ b/aion/web/elm/src/Lobby/Msgs.elm @@ -0,0 +1,8 @@ +module Lobby.Msgs exposing (..) + +import Lobby.Models exposing (LobbyData) +import RemoteData exposing (WebData) + + +type LobbyMsg + = OnFetchRooms (WebData LobbyData) diff --git a/aion/web/elm/src/Lobby/Update.elm b/aion/web/elm/src/Lobby/Update.elm new file mode 100644 index 0000000..13b2532 --- /dev/null +++ b/aion/web/elm/src/Lobby/Update.elm @@ -0,0 +1,21 @@ +module Lobby.Update exposing (..) + +import Lobby.Models exposing (LobbyData, initLobbyData) +import Lobby.Msgs exposing (LobbyMsg(OnFetchRooms)) +import RemoteData + + +update : LobbyMsg -> LobbyData -> ( LobbyData, Cmd LobbyMsg ) +update msg model = + case msg of + OnFetchRooms response -> + let + lobbyData = + case response of + RemoteData.Success rooms -> + rooms + + _ -> + initLobbyData + in + lobbyData ! [] diff --git a/aion/web/elm/src/General/Utils.elm b/aion/web/elm/src/Lobby/Utils.elm similarity index 52% rename from aion/web/elm/src/General/Utils.elm rename to aion/web/elm/src/Lobby/Utils.elm index 6030598..f451292 100644 --- a/aion/web/elm/src/General/Utils.elm +++ b/aion/web/elm/src/Lobby/Utils.elm @@ -1,8 +1,8 @@ -module General.Utils exposing (..) +module Lobby.Utils exposing (..) -import General.Msgs exposing (GeneralMsg) -import Array exposing (Array, fromList) import Html exposing (Html, text) +import Lobby.Models exposing (LobbyData, Room, RoomId) +import Lobby.Msgs exposing (LobbyMsg) import RemoteData exposing (WebData) @@ -19,7 +19,7 @@ sliceList n list = (List.take n list) :: (sliceList n (List.drop n list)) -displayWebData : WebData a -> (a -> Html GeneralMsg) -> Html GeneralMsg +displayWebData : WebData a -> (a -> Html LobbyMsg) -> Html LobbyMsg displayWebData webData fun = case webData of RemoteData.NotAsked -> @@ -33,3 +33,19 @@ displayWebData webData fun = RemoteData.Failure error -> text (toString error) + + +getRoomNameById : LobbyData -> RoomId -> String +getRoomNameById model roomId = + case (getRoomById model roomId) of + Just room -> + "Room# " ++ room.name + + _ -> + "Room Not Found" + + +getRoomById : LobbyData -> RoomId -> Maybe Room +getRoomById model roomId = + List.filter (\room -> room.id == roomId) model.data + |> List.head diff --git a/aion/web/elm/src/General/View.elm b/aion/web/elm/src/Lobby/View.elm similarity index 69% rename from aion/web/elm/src/General/View.elm rename to aion/web/elm/src/Lobby/View.elm index 5fe1bdf..89b13f8 100644 --- a/aion/web/elm/src/General/View.elm +++ b/aion/web/elm/src/Lobby/View.elm @@ -1,29 +1,27 @@ -module General.View exposing (..) +module Lobby.View exposing (..) import Bootstrap.Grid as Grid import Bootstrap.Grid.Col as Col -import General.Models exposing (Model) -import General.Msgs exposing (GeneralMsg) -import General.Utils exposing (displayWebData, sliceList) +import Lobby.Utils exposing (displayWebData, sliceList) import Html exposing (Html, a, br, button, div, h2, h3, h4, hr, i, img, li, p, span, text, ul) import Html.Attributes exposing (class, href, src, style) -import Msgs exposing (Msg) -import RemoteData exposing (WebData) -import Room.Models exposing (RoomsData, Room) +import Lobby.Models exposing (LobbyData, Room) +import Lobby.Msgs exposing (LobbyMsg) +asGridContainer : List (Html msg) -> Html msg asGridContainer data = Grid.container [] data -roomListView : Model -> Html GeneralMsg -roomListView model = +lobbyView : LobbyData -> Html LobbyMsg +lobbyView model = div [] [ h4 [ class "room-list-label" ] [ text "Recommended" ] - , displayRooms model.rooms Recommended + , displayRooms model Recommended , hr [ class "room-content-separator" ] [] , h4 [ class "room-list-label" ] [ text "All rooms" ] - , displayRooms model.rooms All + , displayRooms model All ] @@ -32,7 +30,7 @@ type FilterType | All -displayRooms : WebData RoomsData -> FilterType -> Html GeneralMsg +displayRooms : LobbyData -> FilterType -> Html LobbyMsg displayRooms rooms filterType = let fun = @@ -43,10 +41,10 @@ displayRooms rooms filterType = All -> listRooms in - div [] [ displayWebData rooms fun ] + div [] [ fun rooms ] -listRooms : RoomsData -> Html GeneralMsg +listRooms : LobbyData -> Html LobbyMsg listRooms rooms = rooms |> .data @@ -56,23 +54,23 @@ listRooms rooms = |> asGridContainer -listRecommendedRooms : RoomsData -> Html GeneralMsg +listRecommendedRooms : LobbyData -> Html LobbyMsg listRecommendedRooms rooms = listRooms { rooms | data = List.take 6 rooms.data } -listRoomsSlice : List Room -> Html GeneralMsg +listRoomsSlice : List Room -> Html LobbyMsg listRoomsSlice rooms = Grid.row [] (List.map listSingleRoom rooms) -listSingleRoom : Room -> Grid.Column GeneralMsg +listSingleRoom : Room -> Grid.Column LobbyMsg listSingleRoom room = Grid.col [ Col.lg2, Col.md4 ] [ div [ class "tile" ] [ displayRoomLabel room ] ] -displayRoomLabel : Room -> Html GeneralMsg +displayRoomLabel : Room -> Html LobbyMsg displayRoomLabel room = let url = diff --git a/aion/web/elm/src/Models.elm b/aion/web/elm/src/Models.elm new file mode 100644 index 0000000..d07aeed --- /dev/null +++ b/aion/web/elm/src/Models.elm @@ -0,0 +1,82 @@ +module Models exposing (..) + +import Auth.Models exposing (AuthData, Token, UnauthenticatedViewToggle(LoginView), initAuthData, loginForm, registrationForm) +import Bootstrap.Navbar as Navbar +import Lobby.Models exposing (LobbyData, initLobbyData) +import Msgs exposing (Msg(NavbarMsg)) +import Navigation exposing (Location) +import Panel.Models exposing (CategoriesData, PanelData, categoryForm, initPanelData, questionForm, roomForm) +import Room.Models exposing (CurrentQuestion, EventLog, ProgressBar, RoomData, RoomState(QuestionBreak), UserGameData, UserList, initRoomData) +import Ranking.Models exposing (RankingData, initRankingData) +import Toasty +import Toasty.Defaults +import User.Models exposing (UserData, initUserData) + + +type alias Model = + { authData : AuthData + , lobbyData : LobbyData + , panelData : PanelData + , rankingData : RankingData + , roomData : RoomData + , userData : UserData + , route : Route + , toasties : Toasty.Stack Toasty.Defaults.Toast + , navbarState : Navbar.State + , location : Location + } + + +type alias Flags = + { token : Token + } + + +type Route + = AuthRoute + | LobbyRoute + | RoomRoute Int + | CreateRoomRoute + | RankingRoute + | UserRoute + | NotFoundRoute + + +initModel : Flags -> Route -> Location -> Model +initModel flags route location = + let + ( navbarState, _ ) = + Navbar.initialState NavbarMsg + + maybeToken = + initToken flags + + token = + case maybeToken of + Just actualToken -> + actualToken + + Nothing -> + "" + in + { authData = initAuthData location maybeToken + , lobbyData = initLobbyData + , panelData = initPanelData location token + , rankingData = initRankingData location + , roomData = initRoomData location token + , userData = initUserData location + , route = route + , toasties = Toasty.initialState + , navbarState = navbarState + , location = location + } + + +initToken : Flags -> Maybe Token +initToken flags = + case String.isEmpty flags.token of + True -> + Nothing + + False -> + Just flags.token diff --git a/aion/web/elm/src/Msgs.elm b/aion/web/elm/src/Msgs.elm index 55ce5e9..b292d29 100644 --- a/aion/web/elm/src/Msgs.elm +++ b/aion/web/elm/src/Msgs.elm @@ -1,26 +1,12 @@ module Msgs exposing (..) -import Auth.Models exposing (RegistrationResultData) import Auth.Msgs exposing (AuthMsg) import Bootstrap.Navbar as Navbar -import Dom exposing (Error) -import General.Msgs exposing (GeneralMsg) -import Http +import Lobby.Msgs exposing (LobbyMsg) import Navigation exposing (Location) -import Multiselect -import Panel.Models exposing (CategoriesData, CategoryCreatedData, QuestionCreatedData, RoomCreatedData) import Panel.Msgs exposing (PanelMsg) -import Ranking.Models exposing (Ranking) import Ranking.Msgs exposing (RankingMsg) -import RemoteData exposing (WebData) -import Phoenix.Socket -import Json.Encode as Encode -import Room.Models exposing (RoomId, RoomsData) import Room.Msgs exposing (RoomMsg) -import Time exposing (Time) -import Toasty -import Toasty.Defaults -import User.Models exposing (CurrentUser, UserScores) import User.Msgs exposing (UserMsg) @@ -34,4 +20,4 @@ type Msg | MkUserMsg UserMsg | MkPanelMsg PanelMsg | MkAuthMsg AuthMsg - | MkGeneralMsg GeneralMsg + | MkLobbyMsg LobbyMsg diff --git a/aion/web/elm/src/Panel/Api.elm b/aion/web/elm/src/Panel/Api.elm index 98b8898..51a1f41 100644 --- a/aion/web/elm/src/Panel/Api.elm +++ b/aion/web/elm/src/Panel/Api.elm @@ -3,13 +3,14 @@ module Panel.Api exposing (..) import Forms import Http exposing (Body, Error(BadStatus), Request) import Json.Decode as Decode +import Lobby.Models exposing (RoomList) import Navigation exposing (Location) import Panel.Msgs exposing (PanelMsg(OnCategoryCreated, OnFetchCategories, OnQuestionCreated, OnRoomCreated)) import RemoteData exposing (WebData) import Panel.Decoders exposing (categoriesDecoder, categoryCreatedDecoder, questionCreatedDecoder, roomCreatedDecoder) import Json.Encode as Encode import Panel.Models exposing (CategoriesData, CategoryCreatedData, CategoryForm, QuestionCreatedData, QuestionForm, RoomCreatedData, RoomForm) -import Room.Models exposing (RoomsData) +import Room.Models exposing (UserList) import Urls exposing (categoriesUrl, questionsUrl, hostname, roomsUrl) @@ -57,11 +58,11 @@ createQuestionWithAnswersRequest url token decoder body = } -createQuestionWithAnswers : Location -> String -> QuestionForm -> WebData RoomsData -> Cmd PanelMsg -createQuestionWithAnswers location token form rooms = +createQuestionWithAnswers : Location -> String -> QuestionForm -> Cmd PanelMsg +createQuestionWithAnswers location token form = let body = - questionCreationEncoder form rooms + questionCreationEncoder form url = questionsUrl location @@ -71,8 +72,8 @@ createQuestionWithAnswers location token form rooms = |> Cmd.map OnQuestionCreated -questionCreationEncoder : QuestionForm -> WebData RoomsData -> Http.Body -questionCreationEncoder form rooms = +questionCreationEncoder : QuestionForm -> Http.Body +questionCreationEncoder form = let questionValue = Forms.formValue form "question" diff --git a/aion/web/elm/src/Panel/Models.elm b/aion/web/elm/src/Panel/Models.elm index b2225d4..a3e5cba 100644 --- a/aion/web/elm/src/Panel/Models.elm +++ b/aion/web/elm/src/Panel/Models.elm @@ -1,15 +1,37 @@ module Panel.Models exposing (..) +import Auth.Models exposing (Token) import Forms +import Navigation exposing (Location) import Panel.Validators exposing (answersValidations, categoryNameValidations, questionValidations, categoryValidations) import Multiselect +import RemoteData exposing (WebData) +import Toasty +import Toasty.Defaults type alias PanelData = - { questionForm : QuestionForm + { categories : WebData CategoriesData , categoryForm : CategoryForm - , roomForm : RoomForm , categoryMultiSelect : Multiselect.Model + , location : Location + , roomForm : RoomForm + , questionForm : QuestionForm + , toasties : Toasty.Stack Toasty.Defaults.Toast + , token : Token + } + + +initPanelData : Location -> Token -> PanelData +initPanelData location token = + { categories = RemoteData.Loading + , categoryForm = Forms.initForm categoryForm + , categoryMultiSelect = Multiselect.initModel [] "id" + , location = location + , roomForm = Forms.initForm roomForm + , questionForm = Forms.initForm questionForm + , toasties = Toasty.initialState + , token = token } diff --git a/aion/web/elm/src/Panel/Notifications.elm b/aion/web/elm/src/Panel/Notifications.elm index 89deeeb..629534e 100644 --- a/aion/web/elm/src/Panel/Notifications.elm +++ b/aion/web/elm/src/Panel/Notifications.elm @@ -1,8 +1,7 @@ module Panel.Notifications exposing (..) -import General.Models exposing (Model) import Html.Attributes exposing (class) -import Msgs exposing (Msg(..)) +import Panel.Models exposing (PanelData) import Panel.Msgs exposing (PanelMsg(ToastyMsg)) import Toasty import Toasty.Defaults @@ -11,17 +10,17 @@ import Toasty.Defaults -- question form notifications -questionFormValidationErrorToast : ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +questionFormValidationErrorToast : ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) questionFormValidationErrorToast = addToast (Toasty.Defaults.Error "Error!" "Submission form is not valid!") -questionCreationErrorToast : ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +questionCreationErrorToast : ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) questionCreationErrorToast = addToast (Toasty.Defaults.Error "Error!" "Failed to create a question.") -questionCreationSuccessfulToast : ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +questionCreationSuccessfulToast : ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) questionCreationSuccessfulToast = addToast (Toasty.Defaults.Success "Success!" "Question created successfully.") @@ -30,17 +29,17 @@ questionCreationSuccessfulToast = -- category form notifications -categoryFormValidationErrorToast : ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +categoryFormValidationErrorToast : ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) categoryFormValidationErrorToast = addToast (Toasty.Defaults.Error "Error!" "Category submission form is not valid!") -categoryCreationErrorToast : ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +categoryCreationErrorToast : ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) categoryCreationErrorToast = addToast (Toasty.Defaults.Error "Error!" "Failed to create a category.") -categoryCreationSuccessfulToast : ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +categoryCreationSuccessfulToast : ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) categoryCreationSuccessfulToast = addToast (Toasty.Defaults.Success "Success!" "Category created successfully.") @@ -49,17 +48,17 @@ categoryCreationSuccessfulToast = -- room form notifications -roomFormValidationErrorToast : ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +roomFormValidationErrorToast : ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) roomFormValidationErrorToast = addToast (Toasty.Defaults.Error "Error!" "Room submission form is not valid!") -roomCreationErrorToast : ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +roomCreationErrorToast : ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) roomCreationErrorToast = addToast (Toasty.Defaults.Error "Error!" "Failed to create a room.") -roomCreationSuccessfulToast : ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +roomCreationSuccessfulToast : ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) roomCreationSuccessfulToast = addToast (Toasty.Defaults.Success "Success!" "Room created successfully.") @@ -71,6 +70,6 @@ toastsConfig = |> Toasty.containerAttrs [ class "toasty-notification" ] -addToast : Toasty.Defaults.Toast -> ( Model, Cmd PanelMsg ) -> ( Model, Cmd PanelMsg ) +addToast : Toasty.Defaults.Toast -> ( PanelData, Cmd PanelMsg ) -> ( PanelData, Cmd PanelMsg ) addToast toast ( model, cmd ) = Toasty.addToast toastsConfig ToastyMsg toast ( model, cmd ) diff --git a/aion/web/elm/src/Panel/Update.elm b/aion/web/elm/src/Panel/Update.elm index 82a6858..7b1b92d 100644 --- a/aion/web/elm/src/Panel/Update.elm +++ b/aion/web/elm/src/Panel/Update.elm @@ -1,234 +1,139 @@ module Panel.Update exposing (..) import Forms -import General.Models exposing (Model) import Multiselect import Panel.Api exposing (createCategory, createQuestionWithAnswers, createRoom) -import Panel.Models exposing (categoryNamePossibleFields, questionFormPossibleFields) +import Panel.Models exposing (PanelData, categoryNamePossibleFields, questionFormPossibleFields) import Panel.Msgs exposing (PanelMsg(CreateNewCategory, CreateNewQuestionWithAnswers, CreateNewRoom, MultiselectMsg, OnCategoryCreated, OnFetchCategories, OnQuestionCreated, OnRoomCreated, ToastyMsg, UpdateCategoryForm, UpdateQuestionForm, UpdateRoomForm)) import Panel.Notifications exposing (categoryCreationErrorToast, categoryCreationSuccessfulToast, categoryFormValidationErrorToast, questionCreationErrorToast, questionCreationSuccessfulToast, questionFormValidationErrorToast, roomCreationErrorToast, roomCreationSuccessfulToast, roomFormValidationErrorToast, toastsConfig) import RemoteData import Toasty -import UpdateHelpers exposing (unwrapToken, updateForm) +import UpdateHelpers exposing (updateForm) -update : PanelMsg -> Model -> ( Model, Cmd PanelMsg ) +update : PanelMsg -> PanelData -> ( PanelData, Cmd PanelMsg ) update msg model = case msg of OnQuestionCreated response -> case response of RemoteData.Success responseData -> let - oldPanelData = - model.panelData - - oldQuestionForm = - model.panelData.questionForm - newQuestionForm = - updateForm "question" "" oldQuestionForm + updateForm "question" "" model.questionForm |> updateForm "answers" "" in - { model | panelData = { oldPanelData | questionForm = newQuestionForm } } - ! [] - |> questionCreationSuccessfulToast + { model | questionForm = newQuestionForm } ! [] |> questionCreationSuccessfulToast _ -> - model - ! [] - |> questionCreationErrorToast + model ! [] |> questionCreationErrorToast OnCategoryCreated response -> case response of RemoteData.Success responseData -> let - oldPanelData = - model.panelData - - oldCategoryForm = - model.panelData.categoryForm - newCategoryForm = - updateForm "name" "" oldCategoryForm + updateForm "name" "" model.categoryForm in - { model | panelData = { oldPanelData | categoryForm = newCategoryForm } } - ! [] - |> categoryCreationSuccessfulToast + { model | categoryForm = newCategoryForm } ! [] |> categoryCreationSuccessfulToast _ -> - model - ! [] - |> categoryCreationErrorToast + model ! [] |> categoryCreationErrorToast OnRoomCreated response -> case response of RemoteData.Success responseData -> let - oldPanelData = - model.panelData - - oldRoomForm = - model.panelData.roomForm - newRoomForm = - updateForm "name" "" oldRoomForm - |> updateForm "description" "" + updateForm "name" "" model.roomForm |> updateForm "description" "" in - { model | panelData = { oldPanelData | roomForm = newRoomForm } } - ! [] - |> roomCreationSuccessfulToast + { model | roomForm = newRoomForm } ! [] |> roomCreationSuccessfulToast _ -> - model - ! [] - |> roomCreationErrorToast + model ! [] |> roomCreationErrorToast CreateNewCategory -> let - categoryForm = - model.panelData.categoryForm - - token = - unwrapToken model.authData.token - location = model.location validationErrors = categoryNamePossibleFields - |> List.map (\name -> Forms.errorList categoryForm name) + |> List.map (\name -> Forms.errorList model.categoryForm name) |> List.foldr (++) [] |> List.filter (\validations -> validations /= Nothing) in if List.isEmpty validationErrors then - model ! [ createCategory location token categoryForm ] + model ! [ createCategory location model.token model.categoryForm ] else - model - ! [] - |> categoryFormValidationErrorToast + model ! [] |> categoryFormValidationErrorToast CreateNewRoom -> let - roomForm = - model.panelData.roomForm - validationErrors = [] categoryIds = - List.map (\( id, _ ) -> id) (Multiselect.getSelectedValues model.panelData.categoryMultiSelect) - - token = - unwrapToken model.authData.token + List.map (\( id, _ ) -> id) (Multiselect.getSelectedValues model.categoryMultiSelect) location = model.location in if List.isEmpty validationErrors then - model ! [ createRoom location token roomForm categoryIds ] + model ! [ createRoom location model.token model.roomForm categoryIds ] else - model - ! [] - |> roomFormValidationErrorToast + model ! [] |> roomFormValidationErrorToast UpdateQuestionForm name value -> let - oldPanelData = - model.panelData - - questionForm = - oldPanelData.questionForm - updatedQuestionForm = - Forms.updateFormInput questionForm name value + Forms.updateFormInput model.questionForm name value in - { model - | panelData = - { oldPanelData | questionForm = updatedQuestionForm } - } - ! [] + { model | questionForm = updatedQuestionForm } ! [] UpdateCategoryForm name value -> let - oldPanelData = - model.panelData - - categoryForm = - oldPanelData.categoryForm - updatedCategoryForm = - Forms.updateFormInput categoryForm name value + Forms.updateFormInput model.categoryForm name value in - { model - | panelData = - { oldPanelData | categoryForm = updatedCategoryForm } - } - ! [] + { model | categoryForm = updatedCategoryForm } ! [] UpdateRoomForm name value -> let - panelData = - model.panelData - - oldRoomForm = - panelData.roomForm - updatedRoomForm = - Forms.updateFormInput oldRoomForm name value + Forms.updateFormInput model.roomForm name value in - { model - | panelData = - { panelData | roomForm = updatedRoomForm } - } - ! [] + { model | roomForm = updatedRoomForm } ! [] CreateNewQuestionWithAnswers -> let - questionForm = - model.panelData.questionForm - validationErrors = questionFormPossibleFields - |> List.map (\name -> Forms.errorList questionForm name) + |> List.map (\name -> Forms.errorList model.questionForm name) |> List.foldr (++) [] |> List.filter (\validations -> validations /= Nothing) - token = - unwrapToken model.authData.token - location = model.location - - rooms = - model.rooms in if List.isEmpty validationErrors then - model ! [ createQuestionWithAnswers location token questionForm rooms ] + model ! [ createQuestionWithAnswers location model.token model.questionForm ] else - model - ! [] - |> questionFormValidationErrorToast + model ! [] |> questionFormValidationErrorToast OnFetchCategories response -> let - newModel = - { model | categories = response } - categoryList = - case newModel.categories of + case response of RemoteData.Success categoriesData -> List.map (\category -> ( toString (category.id), category.name )) categoriesData.data _ -> [] - oldPanelData = - model.panelData - updatedCategoryMultiselect = Multiselect.initModel categoryList "id" in - { newModel | panelData = { oldPanelData | categoryMultiSelect = updatedCategoryMultiselect } } ! [] + { model | categories = response, categoryMultiSelect = updatedCategoryMultiselect } ! [] ToastyMsg subMsg -> Toasty.update toastsConfig ToastyMsg subMsg model @@ -236,9 +141,6 @@ update msg model = MultiselectMsg subMsg -> let ( subModel, subCmd ) = - Multiselect.update subMsg model.panelData.categoryMultiSelect - - oldPanelData = - model.panelData + Multiselect.update subMsg model.categoryMultiSelect in - { model | panelData = { oldPanelData | categoryMultiSelect = subModel } } ! [ Cmd.map MultiselectMsg subCmd ] + { model | categoryMultiSelect = subModel } ! [ Cmd.map MultiselectMsg subCmd ] diff --git a/aion/web/elm/src/Panel/View.elm b/aion/web/elm/src/Panel/View.elm index 63aef86..ebf859c 100644 --- a/aion/web/elm/src/Panel/View.elm +++ b/aion/web/elm/src/Panel/View.elm @@ -6,11 +6,10 @@ import Bootstrap.Form as Form import Bootstrap.Form.Input as Input import Bootstrap.Form.Select as Select import Forms -import General.Models exposing (Model) import Html exposing (..) import Html.Attributes exposing (class, for, placeholder, type_, value) import Multiselect -import Panel.Models exposing (CategoriesData, Category) +import Panel.Models exposing (CategoriesData, Category, PanelData) import Panel.Msgs exposing (PanelMsg(CreateNewCategory, CreateNewQuestionWithAnswers, CreateNewRoom, MultiselectMsg, ToastyMsg, UpdateCategoryForm, UpdateQuestionForm, UpdateRoomForm)) import Panel.Notifications exposing (toastsConfig) import RemoteData exposing (WebData) @@ -18,11 +17,11 @@ import Toasty import Toasty.Defaults -panelView : Model -> Html PanelMsg +panelView : PanelData -> Html PanelMsg panelView model = div [ class "panel-container" ] [ h5 [] [ text "Create room" ] - , renderRoomForm model.panelData.roomForm model.panelData.categoryMultiSelect + , renderRoomForm model.roomForm model.categoryMultiSelect , Toasty.view toastsConfig Toasty.Defaults.view ToastyMsg model.toasties ] diff --git a/aion/web/elm/src/Ranking/Models.elm b/aion/web/elm/src/Ranking/Models.elm index b9850a3..68d50cb 100644 --- a/aion/web/elm/src/Ranking/Models.elm +++ b/aion/web/elm/src/Ranking/Models.elm @@ -1,11 +1,21 @@ module Ranking.Models exposing (..) +import Navigation exposing (Location) import RemoteData exposing (WebData) type alias RankingData = { data : WebData Ranking , selectedCategoryId : Int + , location : Location + } + + +initRankingData : Location -> RankingData +initRankingData location = + { data = RemoteData.Loading + , selectedCategoryId = -1 + , location = location } diff --git a/aion/web/elm/src/Ranking/Update.elm b/aion/web/elm/src/Ranking/Update.elm index ff89864..7365190 100644 --- a/aion/web/elm/src/Ranking/Update.elm +++ b/aion/web/elm/src/Ranking/Update.elm @@ -1,19 +1,16 @@ module Ranking.Update exposing (..) -import General.Models exposing (Model) import Multiselect +import Ranking.Models exposing (RankingData) import Ranking.Msgs exposing (RankingMsg(OnFetchRanking, OnRankingCategoryChange)) import RemoteData -update : RankingMsg -> Model -> ( Model, Cmd RankingMsg ) +update : RankingMsg -> RankingData -> ( RankingData, Cmd RankingMsg ) update msg model = case msg of OnFetchRanking response -> let - oldRankingData = - model.rankingData - rankingList = case response of RemoteData.Success data -> @@ -30,13 +27,10 @@ update msg model = _ -> -1 in - { model | rankingData = { oldRankingData | data = response, selectedCategoryId = selectedCategoryId } } ! [] + { model | data = response, selectedCategoryId = selectedCategoryId } ! [] OnRankingCategoryChange response -> let - oldRankingData = - model.rankingData - newCategoryId = case (String.toInt response) of Ok result -> @@ -45,4 +39,4 @@ update msg model = _ -> -1 in - { model | rankingData = { oldRankingData | selectedCategoryId = newCategoryId } } ! [] + { model | selectedCategoryId = newCategoryId } ! [] diff --git a/aion/web/elm/src/Ranking/View.elm b/aion/web/elm/src/Ranking/View.elm index 32f6283..3116f0a 100644 --- a/aion/web/elm/src/Ranking/View.elm +++ b/aion/web/elm/src/Ranking/View.elm @@ -1,9 +1,7 @@ module Ranking.View exposing (..) -import General.Models exposing (Model) import Html exposing (..) import Html.Attributes exposing (style, src, value, class) -import Msgs exposing (Msg(..)) import Bootstrap.Table as Table import Bootstrap.Grid as Grid import Bootstrap.Table exposing (rowAttr) @@ -18,7 +16,7 @@ import Ranking.Utils exposing (selectedCategoryScores, sortedScoresWithIndices) import Ranking.Urls exposing (getGoldMedalImageUrl, getSilverMedalImageUrl, getBronzeMedalImageUrl) -rankingView : Model -> Html RankingMsg +rankingView : RankingData -> Html RankingMsg rankingView model = Grid.container [] [ Grid.row [] @@ -36,9 +34,9 @@ rankingView model = ] -selectCategoriesAttributes : Model -> List (Select.Item msg) +selectCategoriesAttributes : RankingData -> List (Select.Item msg) selectCategoriesAttributes model = - case model.rankingData.data of + case model.data of RemoteData.Success rankingData -> List.map displayCategoryOption rankingData.rankingList @@ -51,7 +49,7 @@ displayCategoryOption category = Select.item [ value (toString category.categoryId) ] [ text category.categoryName ] -rankingTable : Model -> Html RankingMsg +rankingTable : RankingData -> Html RankingMsg rankingTable model = Table.table { options = [ Table.striped, Table.hover ] @@ -66,13 +64,13 @@ rankingTable model = } -displayScores : Model -> Table.TBody RankingMsg +displayScores : RankingData -> Table.TBody RankingMsg displayScores model = - case model.rankingData.data of + case model.data of RemoteData.Success rankingData -> let categoryScores = - selectedCategoryScores rankingData.rankingList model.rankingData.selectedCategoryId + selectedCategoryScores rankingData.rankingList model.selectedCategoryId in Table.tbody [] (List.map (displaySingleScore model) (sortedScoresWithIndices categoryScores)) @@ -80,7 +78,7 @@ displayScores model = Table.tbody [] [] -displaySingleScore : Model -> ( Int, PlayerScore ) -> Table.Row RankingMsg +displaySingleScore : RankingData -> ( Int, PlayerScore ) -> Table.Row RankingMsg displaySingleScore model indexAndScore = case indexAndScore of ( index, playerScore ) -> diff --git a/aion/web/elm/src/Room/Api.elm b/aion/web/elm/src/Room/Api.elm deleted file mode 100644 index 33ba4a7..0000000 --- a/aion/web/elm/src/Room/Api.elm +++ /dev/null @@ -1,36 +0,0 @@ -module Room.Api exposing (..) - -import Auth.Models exposing (Token) -import General.Msgs exposing (GeneralMsg(OnFetchRooms)) -import Http exposing (Request) -import Json.Decode as Decode -import Navigation exposing (Location) -import RemoteData -import Room.Decoders exposing (roomsDecoder) -import Room.Models exposing (RoomsData) -import Room.Msgs exposing (RoomMsg) -import Urls exposing (host, roomsUrl) - - -fetchCurrentUserRequest : String -> String -> Decode.Decoder RoomsData -> Request RoomsData -fetchCurrentUserRequest url token decoder = - Http.request - { method = "GET" - , headers = [ Http.header "Authorization" ("Bearer " ++ token) ] - , url = url - , body = Http.emptyBody - , expect = Http.expectJson decoder - , timeout = Nothing - , withCredentials = True - } - - -fetchRooms : Location -> Token -> Cmd GeneralMsg -fetchRooms location token = - let - url = - roomsUrl location - in - fetchCurrentUserRequest url token roomsDecoder - |> RemoteData.sendRequest - |> Cmd.map OnFetchRooms diff --git a/aion/web/elm/src/Room/Decoders.elm b/aion/web/elm/src/Room/Decoders.elm index f565193..c2ef389 100644 --- a/aion/web/elm/src/Room/Decoders.elm +++ b/aion/web/elm/src/Room/Decoders.elm @@ -1,25 +1,11 @@ module Room.Decoders exposing (..) -import Room.Models exposing (Answer, AnswerFeedback, CurrentQuestion, QuestionSummary, Room, RoomsData, UserJoinedInfo, UserLeft, UserList, UserListMessage, UserRecord) +import Lobby.Models exposing (Room) +import Room.Models exposing (Answer, AnswerFeedback, CurrentQuestion, QuestionSummary, UserJoinedInfo, UserLeft, UserList, UserListMessage, UserRecord) import Json.Decode as Decode exposing (field, list, map, null, oneOf) import Json.Decode.Pipeline exposing (decode, required) -roomsDecoder : Decode.Decoder RoomsData -roomsDecoder = - decode RoomsData - |> required "data" (Decode.list (roomDecoder)) - - -roomDecoder : Decode.Decoder Room -roomDecoder = - decode Room - |> required "id" Decode.int - |> required "name" Decode.string - |> required "description" Decode.string - |> required "player_count" Decode.int - - userListMessageDecoder : Decode.Decoder UserListMessage userListMessageDecoder = Decode.map UserListMessage diff --git a/aion/web/elm/src/Room/Models.elm b/aion/web/elm/src/Room/Models.elm index 30c5ad7..e06be4c 100644 --- a/aion/web/elm/src/Room/Models.elm +++ b/aion/web/elm/src/Room/Models.elm @@ -1,15 +1,41 @@ module Room.Models exposing (..) - -type alias RoomId = - Int +import Auth.Models exposing (Token) +import Lobby.Models exposing (RoomId) +import Navigation exposing (Location) +import Phoenix.Socket +import Room.Msgs exposing (RoomMsg) +import Room.Socket exposing (SocketModel, initSocket) +import Toasty +import Toasty.Defaults + + +type alias RoomData = + { currentQuestion : CurrentQuestion + , eventLog : EventLog + , location : Location + , progressBar : ProgressBar + , roomId : Int + , roomState : RoomState + , socket : Phoenix.Socket.Socket RoomMsg + , toasties : Toasty.Stack Toasty.Defaults.Toast + , userList : UserList + , userGameData : UserGameData + } -type alias Room = - { id : RoomId - , name : String - , description : String - , player_count : Int +initRoomData : Location -> Token -> RoomData +initRoomData location token = + { currentQuestion = initCurrentQuestion + , eventLog = initEventLog + , location = location + , roomId = 0 + , roomState = QuestionBreak + , progressBar = initProgressBar + , socket = initSocket token location + , toasties = Toasty.initialState + , userList = [] + , userGameData = initUserGameData } @@ -18,10 +44,6 @@ type RoomState | QuestionBreak -type alias RoomsData = - { data : List Room } - - type alias UserRecord = { name : String , score : Int @@ -45,12 +67,24 @@ type alias UserGameData = { currentAnswer : String } +initUserGameData : UserGameData +initUserGameData = + { currentAnswer = "" } + + type alias CurrentQuestion = { content : String , image_name : ImageName } +initCurrentQuestion : CurrentQuestion +initCurrentQuestion = + { content = "" + , image_name = "" + } + + type alias UserJoinedInfo = { user : String } @@ -79,9 +113,7 @@ type Event type alias UserJoined = - { currentPlayer : String - , newPlayer : String - } + String type alias UserLeft = @@ -100,8 +132,8 @@ asLogIn eventLog event = event :: eventLog -initialLog : EventLog -initialLog = +initEventLog : EventLog +initEventLog = [] @@ -112,8 +144,8 @@ type alias ProgressBar = } -initialProgressBar : ProgressBar -initialProgressBar = +initProgressBar : ProgressBar +initProgressBar = { start = 0.0 , progress = 0.0 , running = Uninitialized @@ -143,3 +175,23 @@ withRunning running bar = withStart : Float -> ProgressBar -> ProgressBar withStart start bar = { bar | start = start } + + +asEventLogIn : RoomData -> EventLog -> RoomData +asEventLogIn model eventLog = + { model | eventLog = eventLog } + + +asProgressBarIn : RoomData -> ProgressBar -> RoomData +asProgressBarIn model bar = + { model | progressBar = bar } + + +cleanRoomData : RoomId -> SocketModel -> RoomData -> RoomData +cleanRoomData roomId socket roomData = + { roomData + | roomId = roomId + , socket = socket + , eventLog = [] + , toasties = Toasty.initialState + } diff --git a/aion/web/elm/src/Room/Notifications.elm b/aion/web/elm/src/Room/Notifications.elm index adedcce..11ecf43 100644 --- a/aion/web/elm/src/Room/Notifications.elm +++ b/aion/web/elm/src/Room/Notifications.elm @@ -1,23 +1,23 @@ module Room.Notifications exposing (..) -import General.Models exposing (Model) import Html.Attributes exposing (class) +import Room.Models exposing (RoomData) import Room.Msgs exposing (RoomMsg(ToastyMsg)) import Toasty import Toasty.Defaults -incorrectAnswerToast : ( Model, Cmd RoomMsg ) -> ( Model, Cmd RoomMsg ) +incorrectAnswerToast : ( RoomData, Cmd RoomMsg ) -> ( RoomData, Cmd RoomMsg ) incorrectAnswerToast = addToast (Toasty.Defaults.Error "Error!" "Wrong answer!") -closeAnswerToast : ( Model, Cmd RoomMsg ) -> ( Model, Cmd RoomMsg ) +closeAnswerToast : ( RoomData, Cmd RoomMsg ) -> ( RoomData, Cmd RoomMsg ) closeAnswerToast = addToast (Toasty.Defaults.Warning "Close one!" "Your answer is almost correct!") -correctAnswerToast : ( Model, Cmd RoomMsg ) -> ( Model, Cmd RoomMsg ) +correctAnswerToast : ( RoomData, Cmd RoomMsg ) -> ( RoomData, Cmd RoomMsg ) correctAnswerToast = addToast (Toasty.Defaults.Success "Good Answer!" "Your answer is correct!") @@ -29,6 +29,6 @@ toastsConfig = |> Toasty.containerAttrs [ class "toasty-notification" ] -addToast : Toasty.Defaults.Toast -> ( Model, Cmd RoomMsg ) -> ( Model, Cmd RoomMsg ) +addToast : Toasty.Defaults.Toast -> ( RoomData, Cmd RoomMsg ) -> ( RoomData, Cmd RoomMsg ) addToast toast ( model, cmd ) = Toasty.addToast toastsConfig ToastyMsg toast ( model, cmd ) diff --git a/aion/web/elm/src/Socket.elm b/aion/web/elm/src/Room/Socket.elm similarity index 78% rename from aion/web/elm/src/Socket.elm rename to aion/web/elm/src/Room/Socket.elm index 2865da5..31061a7 100644 --- a/aion/web/elm/src/Socket.elm +++ b/aion/web/elm/src/Room/Socket.elm @@ -1,5 +1,6 @@ -module Socket exposing (..) +module Room.Socket exposing (..) +import Auth.Models exposing (Token) import Json.Encode import Navigation exposing (Location) import Phoenix.Channel @@ -9,13 +10,21 @@ import Room.Msgs exposing (RoomMsg(ReceiveAnswerFeedback, ReceiveDisplayQuestion import Urls exposing (websocketUrl) -initSocket : String -> Location -> Phoenix.Socket.Socket msg +type alias SocketModel = + Phoenix.Socket.Socket RoomMsg + + +type alias SocketMsg = + Phoenix.Socket.Msg RoomMsg + + +initSocket : Token -> Location -> Phoenix.Socket.Socket msg initSocket token location = Phoenix.Socket.init (websocketUrl location token) |> Phoenix.Socket.withDebug -initializeRoom : Phoenix.Socket.Socket RoomMsg -> String -> ( Phoenix.Socket.Socket RoomMsg, Cmd (Phoenix.Socket.Msg RoomMsg) ) +initializeRoom : SocketModel -> String -> ( SocketModel, Cmd SocketMsg ) initializeRoom socket roomIdToString = let channel = @@ -37,19 +46,12 @@ initializeRoom socket roomIdToString = ) -leaveRoom : - String - -> Phoenix.Socket.Socket RoomMsg - -> ( Phoenix.Socket.Socket RoomMsg, Cmd (Phoenix.Socket.Msg RoomMsg) ) +leaveRoom : String -> SocketModel -> ( SocketModel, Cmd SocketMsg ) leaveRoom roomId socket = Phoenix.Socket.leave ("room:" ++ roomId) socket -sendAnswer : - String - -> Json.Encode.Value - -> Phoenix.Socket.Socket RoomMsg - -> ( Phoenix.Socket.Socket RoomMsg, Cmd (Phoenix.Socket.Msg RoomMsg) ) +sendAnswer : String -> Json.Encode.Value -> SocketModel -> ( SocketModel, Cmd SocketMsg ) sendAnswer roomId payload socket = Phoenix.Push.init "question:new_answer" ("room:" ++ roomId) |> Phoenix.Push.withPayload payload diff --git a/aion/web/elm/src/Room/Subscriptions.elm b/aion/web/elm/src/Room/Subscriptions.elm index efb721c..f6749e7 100644 --- a/aion/web/elm/src/Room/Subscriptions.elm +++ b/aion/web/elm/src/Room/Subscriptions.elm @@ -1,12 +1,11 @@ module Room.Subscriptions exposing (..) -import General.Models exposing (Model) -import Room.Models exposing (RoomState(QuestionBreak, QuestionDisplayed)) +import Room.Models exposing (RoomData, RoomState(QuestionBreak, QuestionDisplayed)) import Room.Msgs exposing (RoomMsg(Tick)) import Time -subscriptions : Model -> Sub RoomMsg +subscriptions : RoomData -> Sub RoomMsg subscriptions model = case model.roomState of QuestionDisplayed -> diff --git a/aion/web/elm/src/Room/Update.elm b/aion/web/elm/src/Room/Update.elm index 0bc62bc..a939ec7 100644 --- a/aion/web/elm/src/Room/Update.elm +++ b/aion/web/elm/src/Room/Update.elm @@ -1,24 +1,22 @@ module Room.Update exposing (..) import Dom exposing (focus) -import General.Models exposing (Model, Route(RoomRoute), asEventLogIn, asProgressBarIn) import Phoenix.Socket -import RemoteData import Room.Constants exposing (answerInputFieldId, enterKeyCode) import Room.Decoders exposing (answerFeedbackDecoder, questionDecoder, questionSummaryDecoder, userJoinedInfoDecoder, userLeftDecoder, userListMessageDecoder) -import Room.Models exposing (Event(MkQuestionSummaryLog, MkUserJoinedLog, MkUserLeftLog), ProgressBarState(Running, Stopped, Uninitialized), RoomState(QuestionBreak, QuestionDisplayed), asLogIn, withProgress, withRunning, withStart) +import Room.Models exposing (Event(MkQuestionSummaryLog, MkUserJoinedLog, MkUserLeftLog), ProgressBarState(Running, Stopped, Uninitialized), RoomData, RoomState(QuestionBreak, QuestionDisplayed), asEventLogIn, asLogIn, asProgressBarIn, withProgress, withRunning, withStart) import Room.Msgs exposing (RoomMsg(FocusResult, KeyDown, NoOperation, OnInitialTime, OnTime, PhoenixMsg, ReceiveAnswerFeedback, ReceiveDisplayQuestion, ReceiveQuestion, ReceiveQuestionBreak, ReceiveQuestionSummary, ReceiveUserJoined, ReceiveUserLeft, ReceiveUserList, SetAnswer, SubmitAnswer, Tick, ToastyMsg)) import Room.Notifications exposing (closeAnswerToast, correctAnswerToast, incorrectAnswerToast, toastsConfig) import Room.Utils exposing (progressBarTick) import UpdateHelpers exposing (decodeAndUpdate) import Json.Encode as Encode -import Socket exposing (leaveRoom, sendAnswer) +import Room.Socket exposing (sendAnswer) import Task import Time exposing (inMilliseconds) import Toasty -update : RoomMsg -> Model -> ( Model, Cmd RoomMsg ) +update : RoomMsg -> RoomData -> ( RoomData, Cmd RoomMsg ) update msg model = case msg of ReceiveUserList raw -> @@ -59,23 +57,13 @@ update msg model = userJoinedInfoDecoder model (\userJoinedInfo -> - let - oldEventLog = - model.eventLog - - log = - case model.user.details of - RemoteData.Success currentUser -> - { currentPlayer = currentUser.name - , newPlayer = userJoinedInfo.user - } - |> MkUserJoinedLog - |> asLogIn oldEventLog - - _ -> - oldEventLog - in - { model | eventLog = log } ! [] + (userJoinedInfo + |> .user + |> MkUserJoinedLog + |> asLogIn model.eventLog + |> asEventLogIn model + ) + ! [] ) ReceiveUserLeft rawUserLeftInfo -> diff --git a/aion/web/elm/src/Room/Utils.elm b/aion/web/elm/src/Room/Utils.elm index 859f803..73bc962 100644 --- a/aion/web/elm/src/Room/Utils.elm +++ b/aion/web/elm/src/Room/Utils.elm @@ -1,38 +1,12 @@ module Room.Utils exposing (..) -import General.Models exposing (Model) +import Lobby.Models exposing (Room, RoomId) import RemoteData import Room.Constants exposing (progressBarTimeout) -import Room.Models exposing (ProgressBar, ProgressBarState(Running, Stopped), Room, RoomId, RoomsData, withProgress, withRunning, withStart) +import Room.Models exposing (ProgressBar, ProgressBarState(Running, Stopped), RoomData, withProgress, withRunning, withStart) import Time exposing (Time, inMilliseconds) -getRoomNameById : Model -> RoomId -> String -getRoomNameById model roomId = - case (getRoomById model roomId) of - Just room -> - "Room# " ++ room.name - - _ -> - "Room Not Found" - - -getRoomById : Model -> RoomId -> Maybe Room -getRoomById model roomId = - List.filter (\room -> room.id == roomId) (getRoomList model) - |> List.head - - -getRoomList : Model -> List Room -getRoomList model = - case model.rooms of - RemoteData.Success roomsData -> - roomsData.data - - _ -> - [] - - progressBarTick : ProgressBar -> Time -> ProgressBar progressBarTick progressBar time = let diff --git a/aion/web/elm/src/Room/View.elm b/aion/web/elm/src/Room/View.elm index 41fc35c..f8a9fea 100644 --- a/aion/web/elm/src/Room/View.elm +++ b/aion/web/elm/src/Room/View.elm @@ -8,30 +8,25 @@ import Bootstrap.Grid.Col as Col import Bootstrap.Grid.Row as Row import Bootstrap.ListGroup as ListGroup import Bootstrap.Progress as Progress -import General.Models exposing (Model) import Html exposing (Attribute, Html, a, button, div, form, h4, hr, input, li, text, ul) import Html.Attributes exposing (autocomplete, class, for, href, id, src, value) import Html.Events exposing (keyCode, on, onClick, onInput, onWithOptions) import Json.Decode exposing (map) -import Msgs exposing (Msg(..)) +import Lobby.Models exposing (RoomId) import Html exposing (Html, a, div, img, li, p, text, ul) import Navigation exposing (Location) import Room.Constants exposing (answerInputFieldId, defaultImagePath, imagesPath) -import Room.Models exposing (Answer, Event(MkQuestionSummaryLog, MkUserJoinedLog, MkUserLeftLog), EventLog, ImageName, ProgressBar, ProgressBarState(Stopped), RoomId, RoomState(QuestionBreak, QuestionDisplayed), RoomsData, UserGameData, UserRecord) +import Room.Models exposing (Answer, Event(MkQuestionSummaryLog, MkUserJoinedLog, MkUserLeftLog), EventLog, ImageName, ProgressBar, ProgressBarState(Stopped), RoomData, RoomState(QuestionBreak, QuestionDisplayed), UserGameData, UserRecord) import Room.Msgs exposing (RoomMsg(SetAnswer, SubmitAnswer, ToastyMsg)) import Room.Notifications exposing (toastsConfig) import Room.Urls exposing (getImageUrl) -import Room.Utils exposing (getRoomList, getRoomNameById) import Toasty import Toasty.Defaults -roomView : Model -> RoomId -> Html RoomMsg +roomView : RoomData -> RoomId -> Html RoomMsg roomView model roomId = let - roomName = - getRoomNameById model roomId - currentAnswer = model.userGameData.currentAnswer in @@ -98,10 +93,7 @@ displaySingleLog event = log = case event of MkUserJoinedLog userJoinedLog -> - if userJoinedLog.currentPlayer == userJoinedLog.newPlayer then - "You have joined the room." - else - userJoinedLog.newPlayer ++ " joined the room." + userJoinedLog ++ " joined the room." MkUserLeftLog userLeftLog -> userLeftLog.user ++ " left." @@ -124,14 +116,14 @@ displaySingleLog event = ListGroup.li [] [ text log ] -displayQuestionText : Model -> Html RoomMsg +displayQuestionText : RoomData -> Html RoomMsg displayQuestionText model = div [ class "question-container" ] [ displayQuestion model.currentQuestion.content model.roomState ] -displayScores : Model -> Html RoomMsg +displayScores : RoomData -> Html RoomMsg displayScores model = div [ class "room-scoreboard" ] diff --git a/aion/web/elm/src/Routing.elm b/aion/web/elm/src/Routing.elm index ed04237..b514d5d 100644 --- a/aion/web/elm/src/Routing.elm +++ b/aion/web/elm/src/Routing.elm @@ -1,6 +1,6 @@ module Routing exposing (..) -import General.Models exposing (Route(AuthRoute, CreateRoomRoute, NotFoundRoute, RankingRoute, RoomListRoute, RoomRoute, UserRoute)) +import Models exposing (Route(AuthRoute, CreateRoomRoute, LobbyRoute, NotFoundRoute, RankingRoute, RoomRoute, UserRoute)) import Navigation exposing (Location) import UrlParser exposing (..) @@ -8,7 +8,7 @@ import UrlParser exposing (..) matchers : Parser (Route -> a) a matchers = oneOf - [ map RoomListRoute top + [ map LobbyRoute top , map AuthRoute (s "auth") , map RoomRoute (s "rooms" int) , map CreateRoomRoute (s "create_room") diff --git a/aion/web/elm/src/Update.elm b/aion/web/elm/src/Update.elm index 4bb4cdd..38ff3f3 100644 --- a/aion/web/elm/src/Update.elm +++ b/aion/web/elm/src/Update.elm @@ -2,23 +2,22 @@ module Update exposing (..) import Auth.Msgs exposing (AuthMsg(LoginResult, RegistrationResult)) import Auth.Update -import General.Api -import General.Models exposing (Model, Route(RankingRoute, UserRoute, RoomListRoute, RoomRoute), asEventLogIn, asProgressBarIn) -import General.Update -import Msgs exposing (Msg(..)) +import Models exposing (Model, Route(LobbyRoute, RankingRoute, RoomRoute, UserRoute)) +import Lobby.Api exposing (fetchRooms) +import Lobby.Update +import Msgs exposing (Msg(LeaveRoom, MkAuthMsg, MkLobbyMsg, MkPanelMsg, MkRankingMsg, MkRoomMsg, MkUserMsg, NavbarMsg, OnLocationChange)) import Panel.Api import Panel.Update import Ranking.Api exposing (fetchRanking) import Ranking.Update import RemoteData -import Room.Api exposing (fetchRooms) +import Room.Models exposing (cleanRoomData) import Room.Msgs exposing (RoomMsg(PhoenixMsg)) +import Room.Socket exposing (initializeRoom, leaveRoom) import Room.Update import Routing exposing (parseLocation) -import Toasty -import Socket exposing (initSocket, initializeRoom, leaveRoom, sendAnswer) -import UpdateHelpers exposing (decodeAndUpdate, postTokenActions, updateForm, withLocation, withToken) -import User.Api exposing (fetchCurrentUser, fetchUserScores) +import UpdateHelpers exposing (postTokenActions, withLocation, withToken) +import User.Api exposing (fetchUserScores) import User.Msgs exposing (UserMsg(Logout)) import User.Update @@ -30,9 +29,9 @@ update msg model = MkRoomMsg subMsg -> let ( updatedModel, cmd ) = - Room.Update.update subMsg model + Room.Update.update subMsg model.roomData in - updatedModel ! [ Cmd.map MkRoomMsg cmd ] + { model | roomData = updatedModel } ! [ Cmd.map MkRoomMsg cmd ] MkUserMsg subMsg -> if User.Api.unauthorized subMsg then @@ -40,9 +39,9 @@ update msg model = else let ( updatedModel, cmd ) = - User.Update.update subMsg model + User.Update.update subMsg model.userData in - updatedModel ! [ Cmd.map MkUserMsg cmd ] + { model | userData = updatedModel } ! [ Cmd.map MkUserMsg cmd ] MkRankingMsg subMsg -> if Ranking.Api.unauthorized subMsg then @@ -50,9 +49,9 @@ update msg model = else let ( updatedModel, cmd ) = - Ranking.Update.update subMsg model + Ranking.Update.update subMsg model.rankingData in - updatedModel ! [ Cmd.map MkRankingMsg cmd ] + { model | rankingData = updatedModel } ! [ Cmd.map MkRankingMsg cmd ] MkPanelMsg subMsg -> if Panel.Api.unauthorized subMsg then @@ -60,24 +59,24 @@ update msg model = else let ( updatedModel, cmd ) = - Panel.Update.update subMsg model + Panel.Update.update subMsg model.panelData in - updatedModel ! [ Cmd.map MkPanelMsg cmd ] + { model | panelData = updatedModel } ! [ Cmd.map MkPanelMsg cmd ] - MkGeneralMsg subMsg -> - if General.Api.unauthorized subMsg then + MkLobbyMsg subMsg -> + if Lobby.Api.unauthorized subMsg then update (MkUserMsg Logout) model else let ( updatedModel, cmd ) = - General.Update.update subMsg model + Lobby.Update.update subMsg model.lobbyData in - updatedModel ! [ Cmd.map MkGeneralMsg cmd ] + { model | lobbyData = updatedModel } ! [ Cmd.map MkLobbyMsg cmd ] MkAuthMsg subMsg -> let ( updatedModel, cmd ) = - Auth.Update.update subMsg model + Auth.Update.update subMsg model.authData extraCmds = case subMsg of @@ -90,7 +89,7 @@ update msg model = _ -> [] in - updatedModel + { model | authData = updatedModel } ! ([ Cmd.map MkAuthMsg cmd ] ++ extraCmds) --the rest @@ -106,14 +105,11 @@ update msg model = RoomRoute roomId -> let ( initializeRoomSocket, initializeRoomCmd ) = - initializeRoom newModel.socket (toString roomId) + initializeRoom newModel.roomData.socket (toString roomId) in { newModel - | eventLog = [] + | roomData = cleanRoomData roomId initializeRoomSocket newModel.roomData , route = newRoute - , roomId = roomId - , socket = initializeRoomSocket - , toasties = Toasty.initialState } ! [ afterLeaveCmd , initializeRoomCmd @@ -121,13 +117,13 @@ update msg model = |> Cmd.map MkRoomMsg ] - RoomListRoute -> + LobbyRoute -> { newModel | route = newRoute } ! [ afterLeaveCmd , fetchRooms |> withLocation model |> withToken model - |> Cmd.map MkGeneralMsg + |> Cmd.map MkLobbyMsg ] RankingRoute -> @@ -156,9 +152,15 @@ update msg model = RoomRoute id -> let ( leaveRoomSocket, leaveRoomCmd ) = - leaveRoom (toString id) model.socket + leaveRoom (toString id) model.roomData.socket + + oldRoomData = + model.roomData + + newRoomData = + { oldRoomData | socket = leaveRoomSocket } in - { model | socket = leaveRoomSocket } + { model | roomData = newRoomData } ! [ leaveRoomCmd |> Cmd.map PhoenixMsg |> Cmd.map MkRoomMsg diff --git a/aion/web/elm/src/UpdateHelpers.elm b/aion/web/elm/src/UpdateHelpers.elm index f0e34ef..6f221af 100644 --- a/aion/web/elm/src/UpdateHelpers.elm +++ b/aion/web/elm/src/UpdateHelpers.elm @@ -2,13 +2,13 @@ module UpdateHelpers exposing (..) import Auth.Models exposing (Token) import Forms -import General.Models exposing (Model) +import Models exposing (Model) import Json.Decode as Decode -import Msgs exposing (Msg(MkGeneralMsg, MkPanelMsg, MkUserMsg)) +import Lobby.Api exposing (fetchRooms) +import Msgs exposing (Msg(MkLobbyMsg, MkPanelMsg, MkUserMsg)) import Navigation exposing (Location, modifyUrl) import Panel.Api exposing (fetchCategories) import Ports exposing (check) -import Room.Api exposing (fetchRooms) import Urls exposing (host) import User.Api exposing (fetchCurrentUser) @@ -21,9 +21,9 @@ updateForm name value form = decodeAndUpdate : Decode.Value -> Decode.Decoder a - -> Model - -> (a -> ( Model, Cmd msg )) - -> ( Model, Cmd msg ) + -> model + -> (a -> ( model, Cmd msg )) + -> ( model, Cmd msg ) decodeAndUpdate encodedValue decoder model updateFun = case Decode.decodeValue decoder encodedValue of Ok value -> @@ -51,7 +51,7 @@ setHomeUrl location = postTokenActions : Token -> Location -> List (Cmd Msg) postTokenActions token location = [ check token - , fetchRooms location token |> Cmd.map MkGeneralMsg + , fetchRooms location token |> Cmd.map MkLobbyMsg , fetchCategories location token |> Cmd.map MkPanelMsg , fetchCurrentUser location token |> Cmd.map MkUserMsg , setHomeUrl location diff --git a/aion/web/elm/src/Urls.elm b/aion/web/elm/src/Urls.elm index c89eb8e..ee12d78 100644 --- a/aion/web/elm/src/Urls.elm +++ b/aion/web/elm/src/Urls.elm @@ -1,53 +1,92 @@ module Urls exposing (..) +import Auth.Models exposing (Token) import Navigation exposing (Location) -hostname : Location -> String +type alias Url = + String + + +type alias Path = + String + + +hostname : Location -> Url hostname location = location.host -host : Location -> String +host : Location -> Url host location = location.protocol ++ "//" ++ (hostname location) ++ "/" -categoriesUrl : Location -> String +categoriesUrl : Location -> Url categoriesUrl location = (host location) ++ "api/categories" -questionsUrl : Location -> String +questionsUrl : Location -> Url questionsUrl location = (host location) ++ "api/questions" -roomsUrl : Location -> String +roomsUrl : Location -> Url roomsUrl location = (host location) ++ "api/rooms?with_counts=true" -rankingUrl : Location -> String +rankingUrl : Location -> Url rankingUrl location = (host location) ++ "api/ranking" -userScoresUrl : Location -> String +userScoresUrl : Location -> Url userScoresUrl location = (host location) ++ "api/user_ranking" -loginUrl : Location -> String +loginUrl : Location -> Url loginUrl location = (host location) ++ "sessions" -registerUrl : Location -> String +registerUrl : Location -> Url registerUrl location = (host location) ++ "register" -websocketUrl : Location -> String -> String +websocketUrl : Location -> Token -> Url websocketUrl location token = "wss://" ++ (hostname location) ++ "/socket/websocket?token=" ++ token + + + +-- local urls +-- calling local urls "paths" in order to distinguish between rankingUrl and rankingPath etc. + + +lobbyPath : Path +lobbyPath = + "#" + + +createRoomPath : Path +createRoomPath = + "#create_room" + + +panelPath : Path +panelPath = + "#panel" + + +userPath : Path +userPath = + "#profile" + + +rankingPath : Path +rankingPath = + "#rankings" diff --git a/aion/web/elm/src/User/Models.elm b/aion/web/elm/src/User/Models.elm index bfb9bf4..43b451e 100644 --- a/aion/web/elm/src/User/Models.elm +++ b/aion/web/elm/src/User/Models.elm @@ -1,11 +1,32 @@ module User.Models exposing (..) +import Navigation exposing (Location) import RemoteData exposing (WebData) +import Urls exposing (Url, host) type alias UserData = { details : WebData CurrentUser , scores : WebData UserScores + , urls : ContextUrls + } + + +initUserData : Location -> UserData +initUserData location = + { details = RemoteData.Loading + , scores = RemoteData.Loading + , urls = initContextUrls location + } + + +type alias ContextUrls = + { avatarPlaceholder : Url + } + + +initContextUrls location = + { avatarPlaceholder = (host location) ++ "placeholders/avatar_placeholder.png" } diff --git a/aion/web/elm/src/User/Update.elm b/aion/web/elm/src/User/Update.elm index a1c0e42..d2e9320 100644 --- a/aion/web/elm/src/User/Update.elm +++ b/aion/web/elm/src/User/Update.elm @@ -1,30 +1,19 @@ module User.Update exposing (..) -import General.Models exposing (Model) import Ports exposing (check) +import User.Models exposing (UserData) import User.Msgs exposing (UserMsg(Logout, OnFetchCurrentUser, OnFetchUserScores)) -update : UserMsg -> Model -> ( Model, Cmd UserMsg ) +update : UserMsg -> UserData -> ( UserData, Cmd UserMsg ) update msg model = case msg of OnFetchCurrentUser response -> - let - oldUserData = - model.user - in - { model | user = { oldUserData | details = response } } ! [] + { model | details = response } ! [] OnFetchUserScores response -> - let - oldUserData = - model.user - in - { model | user = { oldUserData | scores = response } } ! [] + { model | scores = response } ! [] + -- this one should be handled in the top-level update in order to clear out the token field Logout -> - let - oldAuthData = - model.authData - in - { model | authData = { oldAuthData | token = Nothing } } ! [ check "" ] + model ! [] diff --git a/aion/web/elm/src/User/View.elm b/aion/web/elm/src/User/View.elm index 8f01066..f6926c1 100644 --- a/aion/web/elm/src/User/View.elm +++ b/aion/web/elm/src/User/View.elm @@ -1,13 +1,12 @@ module User.View exposing (..) import Bootstrap.Button as Button -import General.Models exposing (Model) import Html exposing (..) import Html.Attributes exposing (class, src) import Navigation exposing (Location) import RemoteData import Urls exposing (host) -import User.Models exposing (CurrentUser, UserCategoryScore) +import User.Models exposing (CurrentUser, UserCategoryScore, UserData) import Bootstrap.Grid as Grid import Bootstrap.Grid.Col as Col import Bootstrap.Badge as Badge @@ -15,9 +14,9 @@ import Bootstrap.Button as Button import User.Msgs exposing (UserMsg(Logout)) -userView : Model -> Html UserMsg +userView : UserData -> Html UserMsg userView model = - case model.user.details of + case model.details of RemoteData.Success user -> renderUserView model user @@ -31,12 +30,12 @@ userView model = text (toString error) -renderUserView : Model -> CurrentUser -> Html UserMsg +renderUserView : UserData -> CurrentUser -> Html UserMsg renderUserView model user = div [ class "profile-container" ] [ Grid.container [] [ Grid.row [] - [ Grid.col [] [ (userDetails model.location user) ] ] + [ Grid.col [] [ (userDetails model user) ] ] , Grid.row [] [ Grid.col [ Col.xs12 ] [ div [ class "userScoreDetails" ] @@ -50,11 +49,11 @@ renderUserView model user = ] -userDetails : Location -> CurrentUser -> Html UserMsg -userDetails location user = +userDetails : UserData -> CurrentUser -> Html UserMsg +userDetails model user = let avatarPlaceholder = - (host location) ++ "placeholders/avatar_placeholder.png" + model.urls.avatarPlaceholder in div [] [ img [ src avatarPlaceholder, class "user-avatar" ] [] @@ -71,9 +70,9 @@ userDetails location user = ] -displayUserScores : Model -> List (Grid.Column UserMsg) +displayUserScores : UserData -> List (Grid.Column UserMsg) displayUserScores model = - case model.user.scores of + case model.scores of RemoteData.Success userScores -> userScores.categoryScores |> List.sortBy .score diff --git a/aion/web/elm/src/View.elm b/aion/web/elm/src/View.elm index e636cd0..a2202a3 100644 --- a/aion/web/elm/src/View.elm +++ b/aion/web/elm/src/View.elm @@ -2,17 +2,17 @@ module View exposing (..) import Auth.View exposing (authView) import Bootstrap.Navbar as Navbar -import General.Constants exposing (footerContent, roomsPath, createRoomPath, userPath, rankingsPath) -import General.Models exposing (Model, Route(AuthRoute, CreateRoomRoute, NotFoundRoute, RankingRoute, RoomListRoute, RoomRoute, UserRoute)) -import General.View exposing (roomListView) +import Constants exposing (footerContent) +import Models exposing (Model, Route(AuthRoute, CreateRoomRoute, LobbyRoute, NotFoundRoute, RankingRoute, RoomRoute, UserRoute)) import Html exposing (..) import Html.Attributes exposing (class, href, src) +import Lobby.View exposing (lobbyView) import Msgs exposing (Msg(..)) import Navigation exposing (Location) import Panel.View exposing (panelView) import Room.View exposing (roomView) import Ranking.View exposing (rankingView) -import Urls exposing (host) +import Urls exposing (createRoomPath, host, lobbyPath, rankingPath, userPath) import User.View exposing (userView) @@ -67,9 +67,9 @@ navbar route location navbarState = _ -> baseNavbar |> Navbar.items - [ Navbar.itemLink [ href roomsPath ] [ text "Rooms" ] + [ Navbar.itemLink [ href lobbyPath ] [ text "Rooms" ] , Navbar.itemLink [ href createRoomPath ] [ text "Create room" ] - , Navbar.itemLink [ href rankingsPath ] [ text "Rankings" ] + , Navbar.itemLink [ href rankingPath ] [ text "Rankings" ] , Navbar.itemLink [ href userPath ] [ text "Profile" ] ] |> Navbar.view navbarState @@ -91,22 +91,22 @@ page model = content = case route of AuthRoute -> - authView model |> Html.map MkAuthMsg + authView model.authData |> Html.map MkAuthMsg - RoomListRoute -> - roomListView model |> Html.map MkGeneralMsg + LobbyRoute -> + lobbyView model.lobbyData |> Html.map MkLobbyMsg RoomRoute id -> - roomView model id |> Html.map MkRoomMsg + roomView model.roomData id |> Html.map MkRoomMsg CreateRoomRoute -> - panelView model |> Html.map MkPanelMsg + panelView model.panelData |> Html.map MkPanelMsg UserRoute -> - userView model |> Html.map MkUserMsg + userView model.userData |> Html.map MkUserMsg RankingRoute -> - rankingView model |> Html.map MkRankingMsg + rankingView model.rankingData |> Html.map MkRankingMsg NotFoundRoute -> notFoundView