From 6c34e829fdcceeb86fb1f76ae0a9724895241b4d Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Fri, 3 Apr 2020 12:50:27 -0500 Subject: [PATCH] WIP --- src/Main.elm | 233 +++++++++++++++++++++++++++++++++++++++++------- src/index.html | 1 + src/styles.styl | 16 ++-- 3 files changed, 214 insertions(+), 36 deletions(-) diff --git a/src/Main.elm b/src/Main.elm index 45874a6..2a8f348 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -1,5 +1,7 @@ module Main exposing (main) +-- import Html.Lazy as HLazy + import Browser import Dict import Html as H exposing (Attribute, Html) @@ -9,9 +11,9 @@ import Http import Json.Decode as J exposing (Decoder) import Json.Encode as Encode import OrderedDict exposing (OrderedDict) -import Url +main : Program () Model Msg main = Browser.element { init = init @@ -166,44 +168,98 @@ view model = header : Model -> Html Msg header model = - H.header (styleGroup [ ( "display", "flex" ), ( "align-items", "center" ) ]) - [ H.h1 (styleGroup [ ( "font-size", "inherit" ), ( "padding", "0.5em" ), ( "width", "100%" ), ( "white-space", "nowrap" ), ( "flex", "1" ) ]) [ H.text "GraphQL Introspector" ] - , H.input - (styleGroup [ ( "width", "100%" ), ( "padding", "0.5em" ), ( "margin", "0.25em 0 0.25em 0" ) ] - ++ [ A.value model.introspectUrl, Ev.onInput UpdateIntrospectUrl, A.placeholder "https://your-graphql-endpoint.example.com/graphql" ] + H.header + (styleGroup [ ( "display", "flex" ), ( "align-items", "center" ) ]) + [ H.h1 + (styleGroup + [ ( "font-size", "inherit" ) + , ( "padding", "0.5em" ) + , ( "width", "100%" ) + , ( "white-space", "nowrap" ) + , ( "flex", "1" ) + ] ) - [] - , H.button (Ev.onClick RequestIntrospection :: styleGroup [ ( "padding", "0.5em" ), ( "margin", "0.25em" ) ]) [ H.text "Add" ] + [ H.text "GraphQL Introspector" ] + , H.form + (Ev.onSubmit RequestIntrospection + :: styleGroup + [ ( "width", "100%" ) + , ( "display", "flex" ) + , ( "align-items", "center" ) + ] + ) + [ H.input + (styleGroup + [ ( "width", "100%" ) + , ( "padding", "0.5em" ) + , ( "margin", "0.25em 0 0.25em 0" ) + ] + ++ [ A.value model.introspectUrl + , Ev.onInput UpdateIntrospectUrl + , A.placeholder "https://your-graphql-endpoint.example.com/graphql" + ] + ) + [] + , H.button + (Ev.onClick RequestIntrospection + :: styleGroup + [ ( "padding", "0.5em" ) + , ( "margin", "0.25em" ) + ] + ) + [ H.text "Add" ] + ] ] introspectionResultView : String -> Maybe IntrospectionResult -> Maybe (Html Msg) introspectionResultView url mir = - case mir of - Just ir -> - Just - (H.li [] - ([ H.text url ] - ++ (case ir of - Loading -> - [ H.text "Loading..." ] - - Error e -> - [ H.text ("Error: " ++ httpErrorToString e) ] - - Success i -> - [ introspectionView i ] - ) - ) + Maybe.map + (\ir -> + H.li + (styleGroup + [ ( "background-color", lighten ) + , ( "padding", "0.5em" ) + , ( "border-radius", "0.5em" ) + , ( "margin", "0 0.25em 0.25em 0.25em" ) + ] ) + (H.header (styleGroup [ ( "margin-bottom", "0.5em" ), ( "border-bottom", "solid 1px #888" ), ( "padding-bottom", "0.75em" ) ]) + [ H.text "" + , H.a + [ A.href url + , A.target "_blank" + , A.title (Debug.toString mir) + ] + [ H.text url ] + ] + :: (case ir of + Loading -> + [ H.text "Loading..." ] - Nothing -> - Nothing + Error e -> + [ H.text ("Error: " ++ httpErrorToString e) ] + + Success i -> + [ introspectionView i ] + ) + ) + ) + mir introspectionView : Introspection -> Html Msg introspectionView i = let + types = + keyBy .name i.types + + queryTypes = + List.filter (\t -> Just t.name == i.queryType) i.types + + mutationTypes = + List.filter (\t -> Just t.name == i.mutationType) i.types + allTypes = List.filter (\t -> @@ -211,20 +267,118 @@ introspectionView i = (List.member t.name <| List.filterMap identity [ i.mutationType, i.queryType ] ) + && not (String.startsWith "__" t.kind) ) i.types + + sg = + styleGroup [ ( "margin", "1em 0" ) ] in - H.div [] (List.map typeView allTypes) + H.div [] + (List.filterMap + identity + [ i.queryType + |> Maybe.andThen + (\q -> + Dict.get q types.dict + |> Maybe.andThen + (\t -> + Maybe.andThen t.fields (\f -> H.div [] (List.map fieldView f)) + ) + ) + , H.div sg + [ H.h2 [] [ H.text "Query Fields" ] + , H.div [] (List.map typeView queryTypes) + ] + , H.div sg + [ H.h2 [] [ H.text "Mutations" ] + , H.div [] (List.map typeView mutationTypes) + ] + , H.div sg + [ H.h2 [] [ H.text "All Types" ] + , H.div [] (List.map typeView allTypes) + ] + ] + ) + + +kindColor : String -> String +kindColor s = + case s of + "OBJECT" -> + "#0af" + + "SCALAR" -> + "#fa0" + + "ENUM" -> + "#f0a" + + "INTERFACE" -> + "#af0" + + "UNION" -> + "#0fa" + + "INPUT_OBJECT" -> + "#a0f" + + _ -> + "#fff" typeView : QLType -> Html Msg typeView qt = H.div [] - [ H.text (qt.name ++ ": " ++ qt.kind) ] + ([ H.div + (styleGroup + [ ( "display", "flex" ) + , ( "align-items", "center" ) + , ( "font-size", "16px" ) + , ( "margin", "0.25em 0" ) + ] + ) + [ H.code + (styleGroup + [ ( "margin-right", "0.5em" ) + , ( "color", "#111" ) + , ( "font-size", "75%" ) + , ( "display", "inline-block" ) + , ( "padding", "0.25em" ) + , ( "border-radius", "0.25em" ) + , ( "line-height", "1" ) + , ( "background-color", kindColor qt.kind ) + ] + ) + [ H.text qt.kind ] + , H.code [ A.title (Debug.toString qt) ] [ H.text qt.name ] + ] + ] + ++ (case qt.description of + Just s -> + [ H.p (styleGroup [ ( "margin-left", "2em" ) ]) [ H.text s ] ] + + Nothing -> + [] + ) + ++ (case qt.fields of + Just fields -> + [ H.div (styleGroup [ ( "margin-left", "4em" ) ]) <| List.map fieldView fields ] + + Nothing -> + [] + ) + ) + + +fieldView : Field -> Html Msg +fieldView f = + H.li [ A.title <| Debug.toString f ] [ H.text f.name ] httpErrorToString : Http.Error -> String httpErrorToString e = + -- TODO: refactor as expect function case e of Http.BadUrl s -> "Bad URL: " ++ s @@ -248,8 +402,8 @@ decodeIntrospection = (J.field "__schema" (J.map5 Introspection (J.field "directives" (J.list decodeDirective)) - (J.maybe (J.field "queryType" (J.field "name" J.string))) (J.maybe (J.field "mutationType" (J.field "name" J.string))) + (J.maybe (J.field "queryType" (J.field "name" J.string))) (J.maybe (J.field "subscriptionType" (J.field "name" J.string))) (J.field "types" (J.list decodeType)) ) @@ -280,14 +434,17 @@ decodeField = (J.maybe (J.field "deprecationReason" J.string)) +decodePossibleType : Decoder PossibleType decodePossibleType = decodeArgType +decodeEnumValue : Decoder J.Value decodeEnumValue = J.value +decodeInterface : Decoder J.Value decodeInterface = J.value @@ -310,6 +467,7 @@ decodeArg = (J.field "type" decodeArgType) +decodeFieldType : Decoder ArgType decodeFieldType = J.map3 ArgType (J.field "kind" J.string) @@ -333,9 +491,24 @@ decodeLazyArgType = introspectionQuery : Http.Body introspectionQuery = -- this query was copied from the one graphiql made - Http.jsonBody (Encode.object [ ( "query", Encode.string "query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description args { ...InputValue }}}} fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef }}fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue}fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name }}}}}}}}" ) ]) + Http.jsonBody (Encode.object [ ( "query", Encode.string "query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description args { ...InputValue }}}} fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef }} fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue} fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name }}}}}}}}" ) ]) styleGroup : List ( String, String ) -> List (Attribute Msg) styleGroup l = List.map (\( k, v ) -> A.style k v) l + + +lighten : String +lighten = + "rgba(255, 255, 255, 0.05)" + + +keyBy : (a -> comparable) -> List a -> OrderedDict comparable a +keyBy f l = + List.foldl (\t -> \d -> OrderedDict.insert (f t) t d) OrderedDict.empty l + + +getByKey : comparable -> OrderedDict comparable v -> Maybe v +getByKey k d = + Dict.get k d.dict diff --git a/src/index.html b/src/index.html index 0ca90b1..676489b 100644 --- a/src/index.html +++ b/src/index.html @@ -1,6 +1,7 @@ + GraphQL Introspector diff --git a/src/styles.styl b/src/styles.styl index 03e005a..dfae476 100644 --- a/src/styles.styl +++ b/src/styles.styl @@ -1,11 +1,14 @@ -#app +html, body background #111 color #fff - font-family Iosevka, monospace - display flex - flex-direction column - justify-content space-between - min-height 100vh + +#app + font-family sans-serif + font-size 16px + line-height 1.6em + + pre, code + font-family monospace a color #0af @@ -15,6 +18,7 @@ input, select, button font inherit + color inherit border 0 background #333 border-radius 0.25em