diff --git a/elm-package.json b/elm-package.json index ba03865..211ac77 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,14 +1,17 @@ { - "version": "1.0.0", - "summary": "an elm boilerplate", - "repository": "https://github.com/astroash/elm-spa-boiler-plate.git", - "license": "BSD3", - "source-directories": ["src/elm"], - "exposed-modules": [], - "dependencies": { - "elm-lang/core": "5.1.1 <= v < 6.0.0", - "elm-lang/html": "2.0.0 <= v < 3.0.0", - "elm-lang/navigation": "2.1.0 <= v < 3.0.0" - }, - "elm-version": "0.18.0 <= v < 0.19.0" + "version": "1.0.0", + "summary": "an elm boilerplate", + "repository": "https://github.com/astroash/elm-spa-boiler-plate.git", + "license": "BSD3", + "source-directories": [ + "src/elm" + ], + "exposed-modules": [], + "dependencies": { + "elm-lang/core": "5.1.1 <= v < 6.0.0", + "elm-lang/dom": "1.1.1 <= v < 2.0.0", + "elm-lang/html": "2.0.0 <= v < 3.0.0", + "elm-lang/navigation": "2.1.0 <= v < 3.0.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" } diff --git a/public/assets/IDEO's Field Guide to Human-Centred Design.pdf b/public/assets/IDEO's Field Guide to Human-Centred Design.pdf new file mode 100644 index 0000000..6a18361 Binary files /dev/null and b/public/assets/IDEO's Field Guide to Human-Centred Design.pdf differ diff --git a/public/assets/err.svg b/public/assets/err.svg new file mode 100644 index 0000000..160618b --- /dev/null +++ b/public/assets/err.svg @@ -0,0 +1,12 @@ + +Vector +Created using Figma + + + + + + + + + diff --git a/public/audioMessages.js b/public/audioMessages.js index 9ef6c4f..1b9ac7b 100644 --- a/public/audioMessages.js +++ b/public/audioMessages.js @@ -8,7 +8,9 @@ if (navigator.mediaDevices) { var chunks = []; var mediaRecorder; + app.ports.recordStart.subscribe(function() { + console.log("HELLO"); navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { mediaRecorder = new MediaRecorder(stream); mediaRecorder.start(); @@ -20,7 +22,7 @@ if (navigator.mediaDevices) { var blob = new Blob(chunks, {'type': 'audio/ogg; codecs=opus'}); chunks = []; - // jstoelm + // js to elm var audioURL = window.URL.createObjectURL(blob); console.log("blob", blob) app.ports.audioUrl.send(audioURL); @@ -31,7 +33,11 @@ if (navigator.mediaDevices) { mediaRecorder.ondataavailable = function(e) { chunks.push(e.data); }; - }) + }).catch(function(err) { + // js to elm + console.log("Can't start audio!"); + app.ports.recordError.send("Can't start audio!"); + }); }); app.ports.recordStop.subscribe(function() { diff --git a/src/css/_custom.css b/src/css/_custom.css index 0b86172..606bae8 100644 --- a/src/css/_custom.css +++ b/src/css/_custom.css @@ -10,6 +10,8 @@ body { color: var(--brand-teal); margin: 0; font-weight: 400; + background: linear-gradient(180deg, #FFFFFF 0%, rgba(242, 242, 242, 0.9) 100%); + min-height: 100vh; } button { @@ -22,17 +24,8 @@ h1 { font-weight: 700; } -.main { - background: linear-gradient(180deg, #FFFFFF 0%, rgba(242, 242, 242, 0.9) 100%); - min-height: 667px; - min-width: 375px; - max-height: 736px; - max-width: 414px; -} .main--red { background: var(--red); - max-height: 700px; - max-width: 400px; } .w6 { @@ -67,10 +60,11 @@ h1 { height: var(--height-10); } -/*folded page*/ +/* folded page */ .homeContainer { position: relative; + min-height: 100vh; box-shadow:0 1px 0 rgba(0,0,0,.2); } @@ -78,17 +72,17 @@ h1 { position: absolute; bottom: 0; right: 0; - width: 1em; - height: 1em; + width: 2.5rem; + height: 2.5rem; background-color: #aedeef; } -.homeContainer .corner-right .corner-right-triangle { +.corner-right-triangle { position: absolute; width: 0; height: 0; border-style: solid; - border-width: 0 0 1em 1em; + border-width: 0 0 2.5rem 2.5rem; border-color: transparent transparent #00b5d6 transparent; } @@ -96,7 +90,7 @@ h1 { .iconGrid { display: grid; - /*justify-items: center;*/ + height: 0; grid-template-columns: 1fr 1fr; grid-template-rows: 4fr 4fr 4fr; align-items: center; @@ -206,6 +200,13 @@ h1 { background-position: 14% center; } +.err-checked { + background:url('assets/err.svg') no-repeat; + background-color: var(--red); + background-size: 14%; + background-position: 14% center; +} + .write { background:url('assets/write.svg') no-repeat; background-size: 17%; @@ -244,7 +245,7 @@ h1 { } .mw5half { - max-width: var(--max-width-5half); + /* max-width: var(--max-width-5half); */ } .sans { @@ -252,7 +253,7 @@ h1 { } .mh-100 { - min-height: 100vh; + /* min-height: 100vh; */ } .star { @@ -302,7 +303,7 @@ h1 { margin-left: 12rem; } -.borrowbooks{ +.borrowbooks { background: url("assets/borrowbooks.svg") no-repeat; background-size: 100%; width: 8rem; @@ -312,5 +313,8 @@ h1 { .audio { background-color: var(--red); +} +.footer { + height: 2.5rem; } diff --git a/src/elm/Routes/Home.elm b/src/elm/Routes/Home.elm index 46d34f0..1c650ff 100644 --- a/src/elm/Routes/Home.elm +++ b/src/elm/Routes/Home.elm @@ -32,14 +32,14 @@ home model = , span [ class "db f4" ] [ text "Edinburgh Central Library" ] - , div - [ class "corner-right" ] - [ a [ href "#secondPage" ] [ div [ class "corner-right-triangle" ] [ p [ class "white f4 fr pt3 pr2" ] [ text "1" ] ] ] ] ] ] , a [ class "mt4 db br-100 w9 h9 center bg-bookbug", href "#secondPage" ] [] - , div [ style [ ( "display", "none" ) ] ] - [ Routes.SecondPage.secondPage model ] + , div [ class "footer" ] + [ div + [ class "corner-right" ] + [ a [ href "#secondPage" ] [ div [ class "corner-right-triangle" ] [ p [ class "white f4 fr pr1" ] [ text "1" ] ] ] ] + ] ] diff --git a/src/elm/Routes/ReviewPage.elm b/src/elm/Routes/ReviewPage.elm index afc8adc..70b0522 100644 --- a/src/elm/Routes/ReviewPage.elm +++ b/src/elm/Routes/ReviewPage.elm @@ -8,8 +8,8 @@ import Types exposing (..) reviewPage : Model -> Html Msg reviewPage model = - div [ class <| "center main mw6 homeContainer" ++ (hasChanged model) ] - [ h1 [ class "tc f3 pt6" ] [ text "How would you rate your experience?" ] + div [ class <| "center main homeContainer" ] + [ h1 [ class "tc f3 pa4" ] [ text "How would you rate your experience?" ] , Html.form [ class "star-rating center pl2 tc pt4" ] [ fieldset [ class "bn" ] [ span [ class "star-group" ] @@ -18,8 +18,11 @@ reviewPage model = ) ] ] - , div [ class "corner-right" ] - [ a [ href "#thirdPage" ] [ div [ class "corner-right-triangle" ] [ p [ class "white f4 fr pr1" ] [ text "3" ] ] ] ] + , div [ class "footer" ] + [ div + [ class "corner-right" ] + [ a [ href "#thirdPage" ] [ div [ class "corner-right-triangle" ] [ p [ class "white f4 fr pr1" ] [ text "3" ] ] ] ] + ] ] @@ -48,6 +51,6 @@ lightStar model = hasChanged : Model -> String hasChanged model = if (model.backgroundColor == True) then - "bg-blue" + " bg-blue" else "" diff --git a/src/elm/Routes/SecondPage.elm b/src/elm/Routes/SecondPage.elm index 5886a98..bf92be9 100644 --- a/src/elm/Routes/SecondPage.elm +++ b/src/elm/Routes/SecondPage.elm @@ -8,11 +8,14 @@ import Types exposing (..) secondPage : Model -> Html Msg secondPage model = - div [ class "center main flex flex-column homeContainer" ] - [ section [] [ h1 [ class "tc f3 pa3 ma4" ] [ text "What brings you here?" ] ] + div [ class "center main homeContainer" ] + [ section [] [ h1 [ class "tc f3 pa3 ma2" ] [ text "What brings you here?" ] ] , createIcons model - , div [ class "corner-right" ] - [ a [ href "#reviewPage" ] [ div [ class "corner-right-triangle" ] [ p [ class "white f4 fr pr1" ] [ text "2" ] ] ] ] + , div [ class "footer" ] + [ div + [ class "corner-right" ] + [ a [ href "#reviewPage" ] [ div [ class "corner-right-triangle" ] [ p [ class "white f4 fr pr1" ] [ text "2" ] ] ] ] + ] ] diff --git a/src/elm/Routes/Sent.elm b/src/elm/Routes/Sent.elm index 2b8610b..3c33ac3 100644 --- a/src/elm/Routes/Sent.elm +++ b/src/elm/Routes/Sent.elm @@ -14,4 +14,9 @@ sent model = , h1 [ class "white sans ma0 f1 pv5 link" ] [ text "Thanks!" ] ] + , div [ class "footer" ] + [ div + [ class "corner-right" ] + [ a [ href "#storyboard" ] [ div [ class "corner-right-triangle" ] [ p [ class "white f4 fr pr1" ] [ text "..." ] ] ] ] + ] ] diff --git a/src/elm/Routes/StoryBoard.Elm b/src/elm/Routes/StoryBoard.Elm index a5f3e28..5dad1ab 100644 --- a/src/elm/Routes/StoryBoard.Elm +++ b/src/elm/Routes/StoryBoard.Elm @@ -7,7 +7,7 @@ import Types exposing (..) storyBoard : Model -> Html Msg storyBoard model = - div [ class "center main mw6 homeContainer mh-100" ] + div [ class "center main homeContainer" ] [ h1 [ class "tc ma0 pt2 b f3 pa2" ] [ text "Story Board" ] , section [ class "bubbles dib pt2" ] [ section [ class "blueBubble" ] [] @@ -22,4 +22,9 @@ storyBoard model = , class "audio" ] [] + , div [ class "footer" ] + [ div + [ class "corner-right" ] + [ a [ href "#home" ] [ div [ class "corner-right-triangle" ] [ p [ class "white f4 fr pr1" ] [ text "5" ] ] ] ] + ] ] diff --git a/src/elm/Routes/ThirdPage.elm b/src/elm/Routes/ThirdPage.elm index 6d56d48..a30db8e 100644 --- a/src/elm/Routes/ThirdPage.elm +++ b/src/elm/Routes/ThirdPage.elm @@ -8,10 +8,15 @@ import Types exposing (..) thirdPage : Model -> Html Msg thirdPage model = - div [ class "center main" ] + div [ class "center main homeContainer" ] [ h1 [ class "tc f3 pa3 ma4" ] [ text "What's your story?" ] , makeIcons model + , div [ class "footer" ] + [ div + [ class "corner-right" ] + [ a [ href "#storyBoard" ] [ div [ class "corner-right-triangle" ] [ p [ class "white f4 fr pr1" ] [ text "4" ] ] ] ] + ] ] @@ -26,7 +31,7 @@ makeIcons model = makeIcon : ( Message, Stage ) -> Model -> Html Msg makeIcon ( message, stage ) model = - div [ class <| (messageToClass ( message, stage )) ++ " pointer ba bw2 br-pill pa4 pl5 tc mw5half center mv5", onClick (messageToMsg ( message, stage )) ] [ text <| messageToText ( message, stage ) model ] + div [ class <| (messageToClass ( message, stage )) ++ " pointer ba bw2 br-pill pa4 pl5 tc mw5half mv5 mh3", onClick (messageToMsg ( message, stage )) ] [ text <| messageToText ( message, stage ) model ] messageToMsg : ( Message, Stage ) -> Msg @@ -78,7 +83,7 @@ messageToText ( message, stage ) model = "SEND IT" _ -> - "" + "error :( click here" messageToClass : ( Message, Stage ) -> String @@ -102,5 +107,14 @@ messageToClass ( message, stage ) = ( Text, Stage2 ) -> "write brand b--brand" - _ -> + ( Video, Stage0 ) -> + "dn" + + ( Video, Stage1 ) -> "dn" + + ( Video, Stage2 ) -> + "dn" + + _ -> + "err-checked b--red white f4" diff --git a/src/elm/State.elm b/src/elm/State.elm index 8678a34..ea13286 100644 --- a/src/elm/State.elm +++ b/src/elm/State.elm @@ -2,6 +2,8 @@ port module State exposing (..) import Types exposing (..) import Time exposing (Time, second) +import Task exposing (..) +import Dom.Scroll exposing (..) -- MODEL @@ -74,6 +76,9 @@ findToggledMessage message ( mappedMessage, mappedStage ) = Stage2 -> ( mappedMessage, Stage0 ) + + StageErr -> + ( mappedMessage, StageErr ) else ( mappedMessage, mappedStage ) @@ -81,6 +86,9 @@ findToggledMessage message ( mappedMessage, mappedStage ) = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of + NoOp -> + ( model, Cmd.none ) + Increment -> if model.messageLength >= 30 then model @@ -90,7 +98,7 @@ update msg model = ( { model | messageLength = model.messageLength + 1 }, Cmd.none ) UrlChange location -> - ( { model | route = (getRoute location.hash) }, Cmd.none ) + ( { model | route = (getRoute location.hash) }, Task.attempt (always NoOp) (toBottom "container") ) ToggleIcon classTuple -> ( { model | reasonForVisiting = (List.map (\n -> findToggledIcon n classTuple) model.reasonForVisiting) }, Cmd.none ) @@ -104,8 +112,14 @@ update msg model = RecieveAudio string -> ( { model | audioMessage = string }, Cmd.none ) + RecordError err -> + ( { model | messageType = [ ( Audio, StageErr ), ( Video, Stage0 ), ( Text, Stage0 ) ] }, Cmd.none ) + ToggleAudio ( message, stage ) -> case stage of + StageErr -> + ( { model | route = StoryBoardRoute, messageType = [ ( Audio, StageErr ), ( Video, Stage0 ), ( Text, Stage0 ) ] }, Cmd.none ) + Stage2 -> ( { model | messageLength = 0, route = SentRoute, messageType = (List.map (\n -> findToggledMessage message n) model.messageType) }, Cmd.none ) @@ -117,6 +131,9 @@ update msg model = ToggleText ( message, stage ) -> case stage of + StageErr -> + ( { model | messageType = [ ( Audio, Stage0 ), ( Video, Stage0 ), ( Text, StageErr ) ] }, Cmd.none ) + Stage2 -> ( { model | messageType = (List.map (\n -> findToggledMessage message n) model.messageType) }, Cmd.none ) @@ -128,6 +145,9 @@ update msg model = ToggleVideo ( message, stage ) -> case stage of + StageErr -> + ( { model | messageType = [ ( Audio, Stage0 ), ( Video, StageErr ), ( Text, Stage0 ) ] }, Cmd.none ) + Stage2 -> ( { model | messageType = (List.map (\n -> findToggledMessage message n) model.messageType) }, Cmd.none ) @@ -153,6 +173,9 @@ port recordStart : String -> Cmd msg port recordStop : String -> Cmd msg +port recordError : (String -> msg) -> Sub msg + + port audioUrl : (String -> msg) -> Sub msg @@ -160,6 +183,7 @@ subscriptions : Model -> Sub Msg subscriptions model = Sub.batch [ audioUrl RecieveAudio + , recordError RecordError , if not model.paused then Time.every second (always Increment) else diff --git a/src/elm/Types.elm b/src/elm/Types.elm index 2529133..4c08417 100644 --- a/src/elm/Types.elm +++ b/src/elm/Types.elm @@ -26,6 +26,7 @@ type Stage = Stage0 | Stage1 | Stage2 + | StageErr type alias Model = @@ -51,6 +52,7 @@ type Msg | ToggleIcon ( String, Bool ) | RecordStart String | RecordStop String + | RecordError String | RecieveAudio String | ToggleAudio ( Message, Stage ) | ToggleVideo ( Message, Stage ) @@ -58,3 +60,4 @@ type Msg | Increment | YellowStarClass Int | Count + | NoOp diff --git a/src/elm/View.elm b/src/elm/View.elm index 410295d..9876876 100644 --- a/src/elm/View.elm +++ b/src/elm/View.elm @@ -42,6 +42,6 @@ view model = FourthPageRoute -> fourthPage model in - div [ class "" ] + div [ class "w-100 fixed overflow-y-scroll top-0 bottom-0", id "container" ] [ page ] diff --git a/usertesting.md b/usertesting.md new file mode 100644 index 0000000..1622c63 --- /dev/null +++ b/usertesting.md @@ -0,0 +1,122 @@ + +# User Testing _*made easy*_ :sunglasses: + +This document is to help with the runnng of a user testing for the Scottish Book Trust prototype. + +Remember, you only need to test __5__ users to get enough feedback. Doesn't feel like enough? [Read this!](https://www.nngroup.com/articles/why-you-only-need-to-test-with-5-users/) + + +## Before you begin :vertical_traffic_light: +* Recruit some users to test with - ideally this will be people who have never used the SBT website but have perhaps attended bookbug sessions or are interested in library advocacy. +* Make sure you have a pad and pen to make notes, or you can also record the session with the candidate’s permission +* Pay particular attention to any strong emotional words, and any usage challenges/unexpected uses of the app (don’t show them how to use it, just watch what they do) - make sure you capture _*everything*_! +* __Any__ user is better than no users, even if they aren't perfect! + + +## Example script :scroll: + Hi, Max. My name is Jen and I'll be guiding you through this session. Before we begin I'll just go over exactly what we'll be doing - to make sure I don't miss anything I'll be reading my notes, if that's OK? + +> *agrees * + +You probably already have a good idea of why we've asked for your help today, but let me go over it again briefly. + +We're asking people to try out our new Scottish Book Trust app that helps people to show their support for libraries. The purpose of our session is to try and see whether the app works as is intended. + +One thing that is _*really*_ important to remember is we're testing the __*site*__, not you! You can’t do anything wrong here. In fact, this is probably the one place today where you don’t have to worry about making mistakes. + +This is only a prototype of the product so some of the functionality is not in place - so if something doesn't work, don't worry. But please let us know what you would expect to happen. + +As you use the site, it's best if you talk out loud as much as you can - tell me what you're looking at, what you want to do and what you're thinking. + +_*Please*_ don't worry about hurting our feelings. We're doing this to improve the site, so we need to hear your honest reactions. + +If you have any questions as we go along, just ask them. I may not be able to answer them right away, since we’re interested in how people use the site when they don’t have someone sitting next to them to help. But if you still have any questions when we’re done I’ll try to answer them then. And if you need to take a break at any point, just let me know. + +One last thing - I'll be taking notes throughout the session to make sure I don't forget anything. + +_*(Note: at this point you can tell the user that you have a microphone or other people watching from a different room etc. And if you want to record them it's best to have them sign a permission slip.)*_ + +Do you have any questions so far? + +> *asks questions or says to continue * +> +Great. Before we look at the site I would like to ask you a few questions. + +_*(Note: These questions aren't really for your benefit but to make the person feel more comfortable. So make this as informal as possible and have a brief informal conversation if you like! If you do have questions you want ask it's best to do this at the end)*_ + +First, what's your occupation? + +> .. +> + +Now, roughly how many hours a week altogether—just a rough estimate— would you say you spend using the Internet, including Web browsing and email, at work and at home? + +> ... +> + +What kinds of sites are you looking at when you browse the Web? + +> ... +> + +Do you have any favorite Web sites? + +>.. +> + +Great, that's it for the questions, we can now open the app. + +_**(Open the app on landing screen)**_ + +First, I would like you to look at this page and tell me what you make of it. What strikes you about it? What do you think? Please don't click on anything yet + +> ... +> +Thanks, now I would like you to do a specific task. Again, please try and think out loud as you go along. + +__*Task*__: Imagine you have gone to an event at your local library. The librarian asks you for five minutes of your time to show support for your library. You are given a link which you follow on your smartphone. Please use this app and try to record a message of support. + +You can make up any information you don't know the answer to. + +> ... +> + +Note: If they ever go silent gently remind them to carry on thinking out loud! Throughout the interview, you can ask questions such as: + - What is this? What is it for? + - What do you think of that? + - What do you expect that will do? + - So, what goes through your mind as you look at this? + - What are you looking for? + - What would you do next? Why? + + +Thank you for that, this was really helpful for us. + +_*(Note: At this point you can ask any follow up questions and see if anyone else observing has follow up questions)*_ + + +__Possible follow up questions:__ +* How does this compare to your expectations/prior experience with libraries? +* What di dyou like about this app? What did you dislike? +* What would have been helpful to allow you to complete this task more easily? +* Is there anything that you would change? +* How would you describe this product to a friend? +* If you had three magic wishes to improve this product, what would they be? +* Ask them to email you with any additional thoughts that come to them afterwards - you often receive some great extra nuggets this way +* Ask if they could recommend anyone else to test with - this is crucial as it’s really time-consuming to recruit testers so you’ll need all the help you can get! + +Do you have any questions for me now that we are done? + +> ... +> +__Goodbye!__ :wave: + +## Tips +* If you can't keep up with what the user is saying it's fine to ask them to slow down! Though this is why it can be best to test in pairs, so one person can take notes while the other speaks to the user. +* If the user says _*'hmmm'*_ etc remind them to speak out loud so you can understand their thoughts +* Explore any issues they may raise +* You don't have to have all the answers! Throw any questions back onto the candidate to understand why they've asked it and why it's important to them. A common question candidates ask is ‘would it then do X…?’ - at this stage, you don’t know (you may have a plan but this is as yet untested!) so you want to find out whether doing X would be useful/helpful to them or not, and why they have asked about it + +## Resources :books: +* [Steve Krug user test script](https://sensible.com/downloads/test-script.pdf) - this document is based on his script +* [Example user test with Steve Krug](https://www.youtube.com/watch?v=QckIzHC99Xc)