Make the measuring behavior more clear

This commit is contained in:
Tessa Kelly 2022-12-05 15:35:49 -07:00
parent 184f9fa64e
commit 1fadb607e5
3 changed files with 204 additions and 65 deletions

View File

@ -1,18 +1,26 @@
module Nri.Ui.QuestionBox.V1 exposing
( QuestionBox
, viewAnchored, viewPointingTo, viewStandalone
, viewAnchored, AnchoredBoxMeasurementState
, initAnchoredBoxState, updateAnchoredBoxState
, Measurements, decodeMeasurements
, Element
, viewPointingTo, viewStandalone
, containerId
, AnchoredBoxState, Measurements, Element, initAnchoredBoxState, alignedToAnchors, subscriptionsForAnchoredBox, decodeMeasurements
, hackyHardcodedOffset
)
{-|
@docs QuestionBox
@docs viewAnchored, viewPointingTo, viewStandalone
@docs containerId
@docs AnchoredBoxState, Measurements, Element, initAnchoredBoxState, alignedToAnchors, subscriptionsForAnchoredBox, decodeMeasurements
@docs viewAnchored, AnchoredBoxMeasurementState
@docs initAnchoredBoxState, updateAnchoredBoxState
@docs Measurements, decodeMeasurements
@docs Element
---
@docs viewPointingTo, viewStandalone
@docs containerId
-}
@ -43,26 +51,23 @@ containerId id =
"Nri-Scaffolding-QuestionBox-" ++ id
type AnchoredBoxState
{-| -}
type AnchoredBoxMeasurementState
= Measuring
| WithOffset Float
initAnchoredBoxState : AnchoredBoxState
{-| Initially, the anchored box will be rendered in the "Measuring" state.
While in the Measuring state, the anchored box will have visibility hidden. This will cause the box to take up vertical space but not show to the user until measurements have been taken.t
-}
initAnchoredBoxState : AnchoredBoxMeasurementState
initAnchoredBoxState =
Measuring
hackyHardcodedOffset : Float -> AnchoredBoxState
hackyHardcodedOffset offset =
WithOffset offset
subscriptionsForAnchoredBox : { onWindowResized : msg } -> Sub msg
subscriptionsForAnchoredBox { onWindowResized } =
Browser.Events.onResize (\_ _ -> onWindowResized)
{-| -}
type alias Measurements =
{ anchors : List Element
, container : Element
@ -70,6 +75,7 @@ type alias Measurements =
}
{-| -}
type alias Element =
{ x : Float
, y : Float
@ -78,8 +84,22 @@ type alias Element =
}
decodeMeasurements : Decoder Measurements
{-| Expects JSON in the format:
{
"anchors" = [ { "x" = 1.2, "y" = .3, "width" = 300.0, "height" = 325.5 } ],
"container" = { "x" = 1.2, "y" = .3, "width" = 300.0, "height" = 325.5 },
"box" = { "x" = 1.2, "y" = .3, "width" = 300.0, "height" = 325.5 }
}
-}
decodeMeasurements : Decode.Value -> Result Decode.Error Measurements
decodeMeasurements =
Decode.decodeValue decodeMeasurements_
decodeMeasurements_ : Decoder Measurements
decodeMeasurements_ =
Decode.map3 Measurements
(Decode.field "anchors" (Decode.list decodeElement))
(Decode.field "container" decodeElement)
@ -95,27 +115,29 @@ decodeElement =
(Decode.field "height" Decode.float)
alignedToAnchors : Measurements -> AnchoredBoxState
alignedToAnchors measurements =
{-
<>
container.x targetMid
<> Target
<>
target.x
<> QuestionBox
centeredBoxOffset
Scaffolding Container
<><>
Viewport maxOffset questionBox.width
-}
{-| Pass measurements (which you will need to set up a port/subscription to acquire) in order to construct an anchored box state.
<>
container.x targetMid
<> Target
<>
target.x
<> QuestionBox
centeredBoxOffset
Scaffolding Container
<><>
Viewport maxOffset questionBox.width
-}
updateAnchoredBoxState : Measurements -> AnchoredBoxMeasurementState
updateAnchoredBoxState measurements =
let
-- the middle of the anchor. this is what the box should
-- be aligned to ideally.
@ -223,7 +245,7 @@ viewStandalone questionBox idString =
]
viewAnchored : QuestionBox msg -> String -> AnchoredBoxState -> List (Html msg) -> Html msg
viewAnchored : QuestionBox msg -> String -> AnchoredBoxMeasurementState -> List (Html msg) -> Html msg
viewAnchored questionBox idString state content =
let
offset_ =

View File

@ -1,4 +1,4 @@
module Examples.QuestionBox exposing (Msg, State, example)
port module Examples.QuestionBox exposing (Msg, State, example)
{-|
@ -6,7 +6,6 @@ module Examples.QuestionBox exposing (Msg, State, example)
-}
import Accessibility.Styled exposing (..)
import Category exposing (Category(..))
import Code
import Css
@ -15,9 +14,19 @@ import Debug.Control.Extra as ControlExtra
import Debug.Control.View as ControlView
import EllieLink
import Example exposing (Example)
import Html.Styled as Html
import Html.Styled exposing (..)
import Html.Styled.Attributes exposing (id)
import Json.Decode
import Json.Encode as Encode
import Markdown
import Nri.Ui.QuestionBox.V1 as QuestionBox
import Nri.Ui.Button.V10 as Button
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Heading.V3 as Heading
import Nri.Ui.Highlightable.V1 as Highlightable
import Nri.Ui.Highlighter.V1 as Highlighter
import Nri.Ui.HighlighterTool.V1 as Tool
import Nri.Ui.QuestionBox.V1 as QuestionBox exposing (QuestionBox)
import Nri.Ui.Spacing.V1 as Spacing
import Nri.Ui.Table.V6 as Table
@ -38,7 +47,7 @@ example =
, version = version
, state = init
, update = update
, subscriptions = \_ -> Sub.none
, subscriptions = subscriptions
, categories = [ Interactions ]
, keyboardSupport = []
, preview = []
@ -70,12 +79,16 @@ view ellieLinkConfig state =
}
]
}
, viewExamplesTable
, Heading.h2
[ Heading.plaintext "Non-interactive examples"
, Heading.css [ Css.marginTop Spacing.verticalSpacerPx ]
]
, viewExamplesTable state
]
viewExamplesTable : Html Msg
viewExamplesTable =
viewExamplesTable : State -> Html Msg
viewExamplesTable state =
Table.view
[ Table.string
{ header = "Pattern"
@ -85,14 +98,14 @@ viewExamplesTable =
, sort = Nothing
}
, Table.custom
{ header = Html.text "About"
, view = .description >> Markdown.toHtml Nothing >> List.map Html.fromUnstyled >> Html.span []
{ header = text "About"
, view = .description >> Markdown.toHtml Nothing >> List.map fromUnstyled >> span []
, width = Css.px 50
, cellStyles = always [ Css.padding2 Css.zero (Css.px 7), Css.verticalAlign Css.top ]
, sort = Nothing
}
, Table.custom
{ header = Html.text "Example"
{ header = text "Example"
, view = .example
, width = Css.pct 75
, cellStyles = always [ Css.textAlign Css.center ]
@ -109,29 +122,29 @@ viewExamplesTable =
, { label = "after the football game", onClick = NoOp }
]
}
"fake-id-string"
"fake-standalone-id-string"
}
, { pattern = "QuestionBox.viewAnchored"
, description = "???"
, example =
QuestionBox.viewAnchored
{ markdown = """
Not quite. **Plural** means **more than one person.**
This subject is **only one person.**
"""
, actions = [ { label = "Try again", onClick = NoOp } ]
}
"fake-id-string"
(QuestionBox.hackyHardcodedOffset 20)
[ Html.text "QuestionBox content"
div []
[ QuestionBox.viewAnchored
{ markdown = "Not quite. Lets back up a bit."
, actions = [ { label = "Show me", onClick = NoOp } ]
}
anchoredExampleId
state.viewAnchoredExampleMeasurements
[ viewHighlighterExample ]
, Button.button "Measure & render"
[ Button.onClick GetAnchoredExampleMeasurements
]
]
}
, { pattern = "QuestionBox.viewPointingTo"
, description = "???"
, example =
QuestionBox.viewPointingTo
[ Html.text "QuestionBox content" ]
[ text "QuestionBox content" ]
{ markdown = "Does this make sense?"
, actions =
[ { label = "Yes", onClick = NoOp }
@ -142,16 +155,57 @@ This subject is **only one person.**
]
viewHighlighterExample : Html msg
viewHighlighterExample =
Highlighter.static
{ id = highlighterExampleId
, highlightables =
[ ( "Spongebob", Nothing )
, ( "has", Nothing )
, ( "a", Nothing )
, ( "beautiful,"
, Just
(Tool.buildMarker
{ highlightColor = Colors.highlightYellow
, hoverColor = Colors.highlightYellow
, hoverHighlightColor = Colors.highlightYellow
, kind = ()
, name = Nothing
}
)
)
, ( "plant", Nothing )
, ( "above", Nothing )
, ( "his", Nothing )
, ( "TV.", Nothing )
]
|> List.intersperse ( " ", Nothing )
|> List.indexedMap (\i ( word, marker ) -> Highlightable.init Highlightable.Static marker i ( [], word ))
}
highlighterExampleId : String
highlighterExampleId =
"question-box-anchored-highlighter-example"
anchoredExampleId : String
anchoredExampleId =
"question-box-anchored-with-offset-example"
{-| -}
init : State
init =
{ attributes = initAttributes
, viewAnchoredExampleMeasurements = QuestionBox.initAnchoredBoxState
}
{-| -}
type alias State =
{ attributes : Control (List ( String, () ))
, viewAnchoredExampleMeasurements : QuestionBox.AnchoredBoxMeasurementState
}
@ -164,6 +218,8 @@ initAttributes =
type Msg
= UpdateControls (Control (List ( String, () )))
| NoOp
| GetAnchoredExampleMeasurements
| GotAnchoredExampleMeasurements QuestionBox.Measurements
{-| -}
@ -175,3 +231,37 @@ update msg state =
NoOp ->
( state, Cmd.none )
GetAnchoredExampleMeasurements ->
( state
, getAnchoredExampleMeasurements
(Encode.object
[ ( "questionBoxId"
, Encode.string (QuestionBox.containerId anchoredExampleId)
)
, ( "containerId"
, Encode.string highlighterExampleId
)
]
)
)
GotAnchoredExampleMeasurements measurements ->
( { state | viewAnchoredExampleMeasurements = QuestionBox.updateAnchoredBoxState measurements }
, Cmd.none
)
port getAnchoredExampleMeasurements : Encode.Value -> Cmd msg
port gotAnchoredExampleMeasurements : (Json.Decode.Value -> msg) -> Sub msg
subscriptions : State -> Sub Msg
subscriptions state =
gotAnchoredExampleMeasurements
(QuestionBox.decodeMeasurements
>> Result.map GotAnchoredExampleMeasurements
>> Result.withDefault NoOp
)

View File

@ -12,6 +12,8 @@
<script>
const app = Elm.Main.init();
// HIGHLIGHTER
// start listening to events when the user starts dragging/clickingdown
app.ports.highlighterListen.subscribe(function (id) {
window.requestAnimationFrame(() => {
@ -61,6 +63,31 @@
app.ports.highlighterOnDocumentUp.send(id);
};
}
// QUESTIONBOX
app.ports.getAnchoredExampleMeasurements.subscribe(function ({ questionBoxId, containerId, anchorIds }) {
const questionBox = document.getElementById(questionBoxId);
const container = document.getElementById(containerId);
const highlightables = document.getElementsByClassName(
"highlighter-highlightable highlighter-highlighted"
);
if (!questionBox || !container || highlightables.length == 0) {
console.warn(
"Could not find DOM element required for correct scaffolding UI positioning"
);
return;
}
app.ports.gotAnchoredExampleMeasurements.send({
anchors: Array.from(highlightables).map((h) =>
h.getBoundingClientRect()
),
container: container.getBoundingClientRect(),
box: questionBox.getBoundingClientRect(),
});
});
</script>
<script src="bundle.js"></script>
</body>