Compare commits

..

No commits in common. "42126c14899c9626a9a2bdd5757794542dd3e860" and "b76e4bdf7d252c3a66f790e57b6dde5d636a9510" have entirely different histories.

5 changed files with 28 additions and 268 deletions

View File

@ -12,7 +12,6 @@
"elm/html": "1.0.0", "elm/html": "1.0.0",
"elm/http": "2.0.0", "elm/http": "2.0.0",
"elm/json": "1.1.2", "elm/json": "1.1.2",
"elm/svg": "1.0.1",
"elm/url": "1.0.0" "elm/url": "1.0.0"
}, },
"indirect": { "indirect": {

View File

@ -1,9 +0,0 @@
module Scylla.Fnv exposing (..)
import Bitwise
hash : String -> Int
hash = String.foldl hashChar 2166136261
hashChar : Char -> Int -> Int
hashChar char h = modBy 4294967295
<| (Bitwise.xor h <| Char.toCode char) * 16777619

View File

@ -2,42 +2,23 @@ module Scylla.Views exposing (..)
import Scylla.Model exposing (..) import Scylla.Model exposing (..)
import Scylla.Sync exposing (..) import Scylla.Sync exposing (..)
import Scylla.Route exposing (..) import Scylla.Route exposing (..)
import Scylla.Fnv as Fnv
import Svg
import Svg.Attributes
import Url.Builder import Url.Builder
import Json.Decode as Decode import Json.Decode as Decode
import Html exposing (Html, div, input, text, button, div, span, a, h2, table, td, tr) import Html exposing (Html, div, input, text, button, div, span, a)
import Html.Attributes exposing (type_, value, href, class, style) import Html.Attributes exposing (type_, value, href)
import Html.Events exposing (onInput, onClick) import Html.Events exposing (onInput, onClick)
import Dict import Dict
stringColor : String -> String
stringColor s =
let
hue = String.fromFloat <| (toFloat (Fnv.hash s)) / 4294967296 * 360
in
"hsl(" ++ hue ++ ", 82%, 71%)"
senderName : String -> String
senderName s =
let
colonIndex = Maybe.withDefault -1
<| List.head
<| String.indexes ":" s
in
String.slice 1 colonIndex s
viewFull : Model -> List (Html Msg) viewFull : Model -> List (Html Msg)
viewFull model = viewFull model =
let let
room r = Maybe.map (\jr -> (r, jr))
<| Maybe.andThen (Dict.get r)
<| Maybe.andThen .join model.sync.rooms
core = case model.route of core = case model.route of
Login -> loginView model Login -> loginView model
Base -> baseView model Nothing Base -> baseView model
Room r -> baseView model <| room r Room r -> Maybe.withDefault (div [] [])
<| Maybe.map (joinedRoomView model r)
<| Maybe.andThen (Dict.get r)
<| Maybe.andThen .join model.sync.rooms
_ -> div [] [] _ -> div [] []
errorList = errorsView model.errors errorList = errorsView model.errors
in in
@ -49,40 +30,23 @@ errorsView = div [] << List.map errorView
errorView : String -> Html Msg errorView : String -> Html Msg
errorView s = div [] [ text s ] errorView s = div [] [ text s ]
baseView : Model -> Maybe (String, JoinedRoom) -> Html Msg baseView : Model -> Html Msg
baseView m jr = baseView m =
let
roomView = case jr of
Just (id, r) -> joinedRoomView m id r
Nothing -> div [] []
in
div [ class "base-wrapper" ]
[ roomListView m
, roomView
]
roomListView : Model -> Html Msg
roomListView m =
let let
rooms = Maybe.withDefault (Dict.empty) <| Maybe.andThen .join <| m.sync.rooms rooms = Maybe.withDefault (Dict.empty) <| Maybe.andThen .join <| m.sync.rooms
roomList = div [ class "rooms-list" ] <| Dict.values <| Dict.map roomListElementView rooms
in in
div [ class "rooms-wrapper" ] div [] <| Dict.values <| Dict.map roomListView rooms
[ h2 [] [ text "Rooms" ]
, roomList
]
roomListElementView : String -> JoinedRoom -> Html Msg roomListView : String -> JoinedRoom -> Html Msg
roomListElementView s jr = roomListView s jr =
let let
name = Maybe.withDefault "<No Name>" <| roomName jr name = Maybe.withDefault "<No Name>" <| roomName jr
in in
a [ href <| Url.Builder.absolute [ "room", s ] [] ] [ text name ] a [ href <| Url.Builder.absolute [ "room", s ] [] ] [ text name ]
loginView : Model -> Html Msg loginView : Model -> Html Msg
loginView m = div [ class "login-wrapper" ] loginView m = div []
[ h2 [] [ text "Log In" ] [ input [ type_ "text", value m.loginUsername, onInput ChangeLoginUsername] []
, input [ type_ "text", value m.loginUsername, onInput ChangeLoginUsername] []
, input [ type_ "password", value m.loginPassword, onInput ChangeLoginPassword ] [] , input [ type_ "password", value m.loginPassword, onInput ChangeLoginPassword ] []
, input [ type_ "text", value m.apiUrl, onInput ChangeApiUrl ] [] , input [ type_ "text", value m.apiUrl, onInput ChangeApiUrl ] []
, button [ onClick AttemptLogin ] [ text "Log In" ] , button [ onClick AttemptLogin ] [ text "Log In" ]
@ -93,51 +57,25 @@ joinedRoomView m roomId jr =
let let
events = Maybe.withDefault [] <| Maybe.andThen .events jr.timeline events = Maybe.withDefault [] <| Maybe.andThen .events jr.timeline
renderedEvents = List.filterMap (eventView m) events renderedEvents = List.filterMap (eventView m) events
eventWrapper = eventWrapperView m renderedEvents eventContainer = eventContainerView m renderedEvents
messageInput = div [ class "message-wrapper" ] messageInput = div []
[ input [ input
[ type_ "text" [ type_ "text"
, onInput <| ChangeRoomText roomId , onInput <| ChangeRoomText roomId
, value <| Maybe.withDefault "" <| Dict.get roomId m.roomText , value <| Maybe.withDefault "" <| Dict.get roomId m.roomText
] [] ] []
, button [ onClick <| SendRoomText roomId ] [ iconView "send" ] , button [ onClick <| SendRoomText roomId ] [ text "Send" ]
] ]
in in
div [ class "room-wrapper" ] div [] [ eventContainer, messageInput ]
[ h2 [] [ text <| Maybe.withDefault "<No Name>" <| roomName jr ]
, eventWrapper
, messageInput
]
iconView : String -> Html Msg eventContainerView : Model -> List (Html Msg) -> Html Msg
iconView name = eventContainerView m = div []
let
url = Url.Builder.absolute [ "static", "svg", "feather-sprite.svg" ] []
in
Svg.svg
[ Svg.Attributes.class "feather-icon"
] [ Svg.use [ Svg.Attributes.xlinkHref (url ++ "#" ++ name) ] [] ]
eventWrapperView : Model -> List (Html Msg) -> Html Msg
eventWrapperView m es = div [ class "events-wrapper" ] [ table [ class "events-table" ] es ]
eventView : Model -> RoomEvent -> Maybe (Html Msg) eventView : Model -> RoomEvent -> Maybe (Html Msg)
eventView m re = eventView m re = case re.type_ of
let "m.room.message" -> messageView m re
viewFunction = case re.type_ of _ -> Nothing
"m.room.message" -> Just messageView
_ -> Nothing
createRow mhtml = tr []
[ td [] [ eventSenderView re.sender ]
, td [] [ mhtml ]
]
in
Maybe.map createRow
<| Maybe.andThen (\f -> f m re) viewFunction
eventSenderView : String -> Html Msg
eventSenderView s =
span [ style "background-color" <| stringColor s, class "sender-wrapper" ] [ text <| senderName s ]
messageView : Model -> RoomEvent -> Maybe (Html Msg) messageView : Model -> RoomEvent -> Maybe (Html Msg)
messageView m re = messageView m re =
@ -152,6 +90,9 @@ messageTextView : Model -> RoomEvent -> Maybe (Html Msg)
messageTextView m re = messageTextView m re =
let let
body = Decode.decodeValue (Decode.field "body" Decode.string) re.content body = Decode.decodeValue (Decode.field "body" Decode.string) re.content
wrap mtext = span [] [ text mtext ] wrap mtext = div []
[ span [] [ text re.sender ]
, span [] [ text mtext ]
]
in in
Maybe.map wrap <| Result.toMaybe body Maybe.map wrap <| Result.toMaybe body

View File

@ -1,170 +0,0 @@
@import url('https://fonts.googleapis.com/css?family=Open+Sans');
$primary-color: #53C0FA;
$primary-color-highlight: #4298C7;
$primary-color-light: #9FDBFB;
$active-input-color: white;
$transition-duration: .125s;
$inactive-input-color: darken($active-input-color, 3%);
$active-input-border-color: $primary-color;
$inactive-input-border-color: darken($inactive-input-color, 10%);
html, body {
height: 100vh;
}
body {
font-family: 'Open Sans', sans-serif;
margin: 0px;
background-color: #fbfbfb;
}
@mixin input-common {
padding: 5px;
border-radius: 3px;
outline: none;
transition: background-color $transition-duration;
}
input {
@include input-common();
background-color: $inactive-input-color;
color: black;
border: .5px solid $inactive-input-border-color;
&:focus {
background-color: $active-input-color;
border-color: $active-input-border-color;
}
}
button {
@include input-common();
border: none;
background-color: $primary-color;
color: white;
text-transform: uppercase;
&:hover, &:focus {
background-color: $primary-color-highlight;
}
}
a {
text-decoration: none;
color: $primary-color;
&:hover {
color: $primary-color-highlight;
}
}
h2 {
margin: 0px;
margin-bottom: 3px;
}
/*
* Login Screen
*/
div.login-wrapper {
display: flex;
flex-direction: column;
max-width: 300px;
margin: auto;
margin-top: 20px;
input, button {
margin: 3px;
}
}
/*
* Base View
*/
div.base-wrapper {
display: flex;
height: 100%;
div {
padding: 3px;
box-sizing: border-box;
}
h2 {
padding-bottom: 5px;
margin-bottom: 5px;
border-bottom: 1px solid $inactive-input-border-color;
}
}
/*
* The list of rooms
*/
div.rooms-wrapper {
flex-shrink: 0;
a {
display: block;
}
}
/*
* The current room, if any.
*/
div.room-wrapper {
flex-grow: 1;
display: flex;
height: 100%;
flex-direction: column;
}
/*
* The message input and send button.
*/
div.message-wrapper {
display: flex;
input {
flex-grow: 9;
margin: 3px;
}
button {
flex-grow: 1;
margin: 3px;
}
}
div.events-wrapper {
overflow-y: scroll;
flex-grow: 1;
}
table.events-table {
td {
padding-left: 5px;
vertical-align: top;
}
}
span.sender-wrapper {
border-radius: 2px;
padding-left: 5px;
padding-right: 5px;
float: right;
}
/**
* Icons
*/
.feather-icon {
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
height: 20px;
width: 20px;
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 54 KiB