Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions morpheus-graphql-server/morpheus-graphql-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ build-type: Simple
extra-source-files:
README.md
changelog.md
src/Data/Morpheus/Server/Playground/index.html
data-files:
test/Feature/Collision/category-collision-fail/query.gql
test/Feature/Collision/category-collision-success/query.gql
Expand Down Expand Up @@ -360,6 +361,7 @@ library
, base >=4.7.0 && <5.0.0
, bytestring >=0.10.4 && <1.0.0
, containers >=0.4.2.1 && <1.0.0
, file-embed >=0.0.10 && <1.0.0
, morpheus-graphql-app >=0.28.0 && <0.29.0
, morpheus-graphql-core >=0.28.0 && <0.29.0
, mtl >=2.0.0 && <3.0.0
Expand Down
68 changes: 4 additions & 64 deletions morpheus-graphql-server/src/Data/Morpheus/Server/Playground.hs
Original file line number Diff line number Diff line change
@@ -1,72 +1,12 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TemplateHaskell #-}

module Data.Morpheus.Server.Playground
( httpPlayground,
)
where

import Data.ByteString.Lazy.Char8 (ByteString)
import Data.Functor (fmap)
import Data.Semigroup ((<>))
import Prelude
( mconcat,
(.),
)

link :: ByteString -> ByteString -> ByteString
link rel href = "<link rel=\"" <> rel <> "\" href=\"" <> href <> "\" />"

meta :: [(ByteString, ByteString)] -> ByteString
meta attr = t "meta" attr []

tag :: ByteString -> [ByteString] -> ByteString
tag tagName = t tagName []

t :: ByteString -> [(ByteString, ByteString)] -> [ByteString] -> ByteString
t tagName attr children =
"<" <> tagName <> " " <> mconcat (fmap renderAttr attr) <> " >" <> mconcat children <> "</" <> tagName <> ">"
where
renderAttr (name, value) = name <> "=\"" <> value <> "\" "

script :: [(ByteString, ByteString)] -> [ByteString] -> ByteString
script = t "script"

html :: [ByteString] -> ByteString
html = docType . t "html" []

docType :: ByteString -> ByteString
docType x = "<!DOCTYPE html>" <> x
import Data.ByteString.Lazy.Char8 (ByteString, fromStrict)
import Data.FileEmbed (embedFile, makeRelativeToProject)

httpPlayground :: ByteString
httpPlayground =
html
[ tag
"head"
[ meta [("charset", "utf-8")],
meta
[ ("name", "viewport"),
("content", "user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui")
],
tag "title" ["GraphQL Playground"],
link "stylesheet" "//cdn.jsdelivr.net/npm/graphql-playground-react/build/static/css/index.css",
link "shortcut icon" "//cdn.jsdelivr.net/npm/graphql-playground-react/build/favicon.png",
script
[("src", "//cdn.jsdelivr.net/npm/graphql-playground-react/build/static/js/middleware.js")]
[]
],
tag
"body"
[ t "div" [("id", "root")] [],
script
[]
[ " window.addEventListener('load', (_) => \
\ GraphQLPlayground.init(document.getElementById('root'), {}) \
\ );"
]
]
]
httpPlayground = fromStrict $(embedFile =<< makeRelativeToProject "src/Data/Morpheus/Server/Playground/index.html")
123 changes: 123 additions & 0 deletions morpheus-graphql-server/src/Data/Morpheus/Server/Playground/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GraphiQL Explorer</title>
<style>
body {
margin: 0;
}

#graphiql {
height: 100dvh;
}

#graphiql:has(.loading) {
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
}

@keyframes spin { to { transform: rotate(1turn); } }
.loading {
position: relative;
display: inline-block;
width: 5em;
height: 5em;
margin: 0 .5em;
font-size: 12px;
text-indent: 999em;
overflow: hidden;
animation: spin 1s infinite steps(8);
}
.loading:before,
.loading:after,
.loading > div:before,
.loading > div:after {
content: '';
position: absolute;
top: 0;
left: 2.25em;
width: .5em;
height: 1.5em;
border-radius: .2em;
background: #eee;
box-shadow: 0 3.5em #eee;
transform-origin: 50% 2.5em;
}
.loading:before {
background: #555;
}
.loading:after {
transform: rotate(-45deg);
background: #777;
}
.loading > div:before {
transform: rotate(-90deg);
background: #999;
}
.loading > div:after {
transform: rotate(-135deg);
background: #bbb;
}
</style>
<link rel="stylesheet" href="https://esm.sh/graphiql/dist/style.css" />
<link rel="stylesheet" href="https://esm.sh/@graphiql/plugin-explorer/dist/style.css" />
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@19.2.4",
"react/": "https://esm.sh/react@19.2.4/",

"react-dom": "https://esm.sh/react-dom@19.2.4",
"react-dom/": "https://esm.sh/react-dom@19.2.4/",

"graphiql": "https://esm.sh/graphiql?standalone&external=react,react-dom,@graphiql/react,graphql",
"graphiql/": "https://esm.sh/graphiql/",
"@graphiql/plugin-explorer": "https://esm.sh/@graphiql/plugin-explorer?standalone&external=react,@graphiql/react,graphql",
"@graphiql/react": "https://esm.sh/@graphiql/react?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",

"@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit?standalone&external=graphql",
"graphql": "https://esm.sh/graphql@16.12.0",
"@emotion/is-prop-valid": "data:text/javascript,"
}
}
</script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import { GraphiQL, HISTORY_PLUGIN } from 'graphiql';
import { createGraphiQLFetcher } from '@graphiql/toolkit';
import { explorerPlugin } from '@graphiql/plugin-explorer';
import 'graphiql/setup-workers/esm.sh';

const wsLocation = window.location;
wsLocation.protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const fetcher = createGraphiQLFetcher({
url: window.location.href,
subscriptionUrl: wsLocation.href,
});
const plugins = [HISTORY_PLUGIN, explorerPlugin()];

function App() {
return React.createElement(GraphiQL, {
fetcher,
plugins,
defaultEditorToolsVisibility: true,
});
}

const container = document.getElementById('graphiql');
const root = ReactDOM.createRoot(container);
root.render(React.createElement(App));
</script>
</head>
<body>
<div id="graphiql">
<div class="loading"><div></div></div>
</div>
</body>
</html>