Skip to content

Commit b46d37e

Browse files
s1naholiman
andauthored
graphql: upgrade UI to v2 (ethereum#27294)
Upgrades graphiql to v2.4.4. The interface has become much nicer, and there are extra features like tabs, history, dark mode etc. This change also now uses golang embed to bundle the resources. --------- Co-authored-by: Martin Holst Swende <martin@swende.se>
1 parent 6fe0252 commit b46d37e

8 files changed

+715
-76
lines changed

graphql/graphiql.go

+44-76
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
package graphql
2424

2525
import (
26-
"bytes"
27-
"fmt"
26+
"encoding/json"
2827
"net/http"
28+
"path/filepath"
29+
30+
"github.com/ethereum/go-ethereum/graphql/internal/graphiql"
31+
"github.com/ethereum/go-ethereum/log"
2932
)
3033

3134
// GraphiQL is an in-browser IDE for exploring GraphiQL APIs.
@@ -34,87 +37,52 @@ import (
3437
// For more information, see https://github.com/graphql/graphiql.
3538
type GraphiQL struct{}
3639

37-
func respond(w http.ResponseWriter, body []byte, code int) {
38-
w.Header().Set("Content-Type", "application/json; charset=utf-8")
40+
func respOk(w http.ResponseWriter, body []byte, ctype string) {
41+
w.Header().Set("Content-Type", ctype)
3942
w.Header().Set("X-Content-Type-Options", "nosniff")
40-
w.WriteHeader(code)
41-
_, _ = w.Write(body)
43+
w.Write(body)
4244
}
4345

44-
func errorJSON(msg string) []byte {
45-
buf := bytes.Buffer{}
46-
fmt.Fprintf(&buf, `{"error": "%s"}`, msg)
47-
return buf.Bytes()
46+
func respErr(w http.ResponseWriter, msg string, code int) {
47+
w.Header().Set("Content-Type", "application/json")
48+
w.WriteHeader(code)
49+
errMsg, _ := json.Marshal(struct {
50+
Error string
51+
}{Error: msg})
52+
w.Write(errMsg)
4853
}
4954

5055
func (h GraphiQL) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5156
if r.Method != http.MethodGet {
52-
respond(w, errorJSON("only GET requests are supported"), http.StatusMethodNotAllowed)
57+
respErr(w, "only GET allowed", http.StatusMethodNotAllowed)
5358
return
5459
}
55-
w.Header().Set("Content-Type", "text/html")
56-
w.Write(graphiql)
60+
switch r.URL.Path {
61+
case "/graphql/ui/graphiql.min.css":
62+
data, err := graphiql.Assets.ReadFile(filepath.Base(r.URL.Path))
63+
if err != nil {
64+
log.Warn("Error loading graphiql asset", "err", err)
65+
respErr(w, "internal error", http.StatusInternalServerError)
66+
return
67+
}
68+
respOk(w, data, "text/css")
69+
case "/graphql/ui/graphiql.min.js",
70+
"/graphql/ui/react.production.min.js",
71+
"/graphql/ui/react-dom.production.min.js":
72+
data, err := graphiql.Assets.ReadFile(filepath.Base(r.URL.Path))
73+
if err != nil {
74+
log.Warn("Error loading graphiql asset", "err", err)
75+
respErr(w, "internal error", http.StatusInternalServerError)
76+
return
77+
}
78+
respOk(w, data, "application/javascript; charset=utf-8")
79+
default:
80+
data, err := graphiql.Assets.ReadFile("index.html")
81+
if err != nil {
82+
log.Warn("Error loading graphiql asset", "err", err)
83+
respErr(w, "internal error", http.StatusInternalServerError)
84+
return
85+
}
86+
respOk(w, data, "text/html")
87+
}
5788
}
58-
59-
var graphiql = []byte(`
60-
<!DOCTYPE html>
61-
<html>
62-
<head>
63-
<link
64-
rel="icon"
65-
type="image/png"
66-
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAActpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+QWRvYmUgSW1hZ2VSZWFkeTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KKS7NPQAAB5FJREFUWAm1FmtsnEdxdr/vfC8/mpgEfHYa6gaUJqAihfhVO7UprSokHsn5jKgKiKLGIIEEbSlpJdQLIJw+UFFQUSuBWir1z9nnpEmgCUnkcxPSmDRCgkKpGoJpfXdxHSc4ftzr2x1m9rvPPQdDDSgrfd/O7szOe2YX4H8cGEtY3tFK2Nu7pjMCChbgzVfD11h4XLKAibahL6dbBv+SaRl6LUsw78XBxTG80mEsWSkxu1oM9qmJlkR7UPhPWSDJCzSISw5zXZGxvpMezUp5GmtWQszunpiAKiPPZ20KyCqY1/ncgs4v+IUPwLJvYhzTVIbmvXgvqwAxkImKJHt1yzM+AQLXvdKXy3QevB4R+3O6wIYHSUCIlABEtTO9bf86pmFa7B6xPeHMi3l668p5SQjInbRGQQw0E3FMH4FHaFPoP8USVaveEo9aaH3LsdRh2vsYKqwhMhRBKw82vGbNQbcC9ePL1+PDmwf7iix0N+xmPoafq4TgDDaRYxmLCrBwD5HpSK4vKRVeP9b3ZyaaaE18UaL4KYE5x5afsWxoBgefFfX+jX6pMH9RvSnX2v1YxPP4D3UAHG2hgm80vRp7ns9nWxOb8kIt3HD6C+O8rpRVoYCxHDOtQwOg4QHS1kIb9oHGVQJlN0h8qPF07FFmkG4byouAjEdSO/bwOntr8kGt8EeNJ3uN27O37fse5PT3lVIjUsrL6MB2IVCThMcbx3ofIt7sZeMFExeTubSR3Zq4tVoEdhHSJs30WqjbIS1Zk6/VqzzhmdbBpyn5p1g4W8LMGkajj9GUSfcM/4IVaji+/QdOa7hehKz69xEPsllLkFZY+HdlWhOdLNxrXm5iTK1xPSHEeo4KxTFPzEsFLHH8D914rG+GGWe2Dd9UJav6ZbW1k9ep7rgF3SnTEUXA3hko2fdkowc2M27dk3deomgfLBIPYlJytC4QLzKLZdAoy3QzNTVqksT2y6Oz+YVL1TK4Oo9FYAVIkRFzgH8F/bOiD0cjv4m+hEA9IdXn8HaC4Mjxzx7OdCZH8R14mra6eB9sfUKTj4SCQLUvCHMqN235rKMGV5ZpPCAoSzGOcs2JaFZYVuc8FF5XQl8uCHV75FT0ZT6Q6Ry+02fZ3b7agLF+MGbYmF/Mg+vE14NY1Xnhjv2fZkTkWO+R2VXqc1BrLczp/OtULV0fOLXjHS5LlvkuhzL05oZf+xnMbtv3BLXZIwyPQNx4iRLvrXRXci/vcV/guXJ4dZ/elnwqfctQlnFxoGyhkY2+eCbTlnyCYU8GwzzcHHBhmKl7261X1CEBaIT0QNxJdyQfpLRdHblt4wNMeuhsVpWPvDulqAXQKH5i9f0Ut7pMT/LhOEWc96hfkBEYYnhDU3DJ2SUKMAEPIagRoTSJObF9uF5oHAC/uF/ENxeRrPcai0vt/k1mE+6GeE9eVIlvQwF+yGfL/KiNuMpUnmF4WQUYwX3AEEzjXmqi5yOp6DO8hrM7TeIZ+Orf2X6DY1oU+FeY1D8xJLh8G2bcsgpQ3vqoAU1P3nWouQaDd8mQdS8Tj1B/Z0sZXm6QyxbvAFlj3Us95e7Jbx6/EYScpnP/kjfMwy3DMre6mXVGIVTqiqi1mtVk8blZR78UOdGbQqDLheLMjWc54Yt7KSAaUvRwTyrdMXREvFF6VtRZfgrALNOcm8ixZxe9uOgBLsMPnftUIdM+tBFKcLtwxCeJ7GbdHDJlJ6DHYetX8gHfSTTEB4P9WNBb5JRq0VrfwbxZRuVN61pMt56ICz3elWxAB18OS//Nep4MKeowTOU/zMwo8RaV5fVKhs4WN1DzCjkzJV1jBT9K1TB6oWN4bR89arDMz7iTa1ikepxsy+CXqmXol1fUfJ4qwUfeptsXL1JNTFNWXkfmO5ydi8KXBIMWvCYnmbOWmKXr5zpZhHotSbQGp9YO+qkb3h05E3vBk+nmwJopw5SSdVxRsOjiCGhEXSMCMFdTrAdbPikul35PvWAN1adPgqAGz8Kk1FLTX2hlCyF9pHSIQlwnp+x6/yb1t9zu8LgFszJHt5v0K+TakuPmbFnmog2cXBzfbFtyj1b6O4SQ4BP76Zr1k1Etwoe7Ir+N/dwcfo8f3QnbsYR7yAO/kxICdAH1En+km/WxhtPRXZ4sZrOoQBk2npjcmmwu2ipMz6s/MlG6JflVqrC9pN8VqLK+1nhix4u8/3Z7YjXPRHeJ52z3vm7Mq6eISa0UeF/DK7FB3r/w8eGP0Htg4f1noud5TXgy1g1lpQIGQelGyLjbQk3J7TZr8yT7uxzwSfu+oiwdIL//gTKc+4MUltxL/lpPFn+ebvqByFhswAjid+VgTLNnXcGcyHGuY7PmvWUHZ2hlqXgXDRNfbD/YSE+2MeeWYzjZMmw+p+MYpnuSJy/FjtZ5DCvPuI9SFv5/DI4buZxfwZBuH7pnpu0QprcOztM3N9v2K8x2DH+FcZktB/nSWeJZ3v93Y8VasRubmqBoGKF4g6oBwjIQoi/MMDrqHOMamnMFmv6ziw0T97diTb0zHB7OEe4ZlCjf5X2U8vGm09HnKrPbo78mMwu6mjFn9tV713TtvWpZSCX83wr9J1EKd8CrhC26AAAAAElFTkSuQmCC"
67-
/>
68-
<link
69-
rel="stylesheet"
70-
href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.13.0/graphiql.css"
71-
integrity="sha384-Qua2xoKBxcHOg1ivsKWo98zSI5KD/UuBpzMIg8coBd4/jGYoxeozCYFI9fesatT0"
72-
crossorigin="anonymous"
73-
/>
74-
<script
75-
src="https://cdnjs.cloudflare.com/ajax/libs/fetch/3.0.0/fetch.min.js"
76-
integrity="sha384-5B8/4F9AQqp/HCHReGLSOWbyAOwnJsPrvx6C0+VPUr44Olzi99zYT1xbVh+ZanQJ"
77-
crossorigin="anonymous"
78-
></script>
79-
<script
80-
src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.5/umd/react.production.min.js"
81-
integrity="sha384-dOCiLz3nZfHiJj//EWxjwSKSC6Z1IJtyIEK/b/xlHVNdVLXDYSesoxiZb94bbuGE"
82-
crossorigin="anonymous"
83-
></script>
84-
<script
85-
src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.5/umd/react-dom.production.min.js"
86-
integrity="sha384-QI+ql5f+khgo3mMdCktQ3E7wUKbIpuQo8S5rA/3i1jg2rMsloCNyiZclI7sFQUGN"
87-
crossorigin="anonymous"
88-
></script>
89-
<script
90-
src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.13.0/graphiql.min.js"
91-
integrity="sha384-roSmzNmO4zJK9X4lwggDi4/oVy+9V4nlS1+MN8Taj7tftJy1GvMWyAhTNXdC/fFR"
92-
crossorigin="anonymous"
93-
></script>
94-
</head>
95-
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
96-
<div id="graphiql" style="height: 100vh;">Loading...</div>
97-
<script>
98-
function fetchGQL(params) {
99-
return fetch("/graphql", {
100-
method: "post",
101-
body: JSON.stringify(params),
102-
credentials: "include",
103-
}).then(function (resp) {
104-
return resp.text();
105-
}).then(function (body) {
106-
try {
107-
return JSON.parse(body);
108-
} catch (error) {
109-
return body;
110-
}
111-
});
112-
}
113-
ReactDOM.render(
114-
React.createElement(GraphiQL, {fetcher: fetchGQL}),
115-
document.getElementById("graphiql")
116-
)
117-
</script>
118-
</body>
119-
</html>
120-
`)

graphql/internal/graphiql/build.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package graphiql
2+
3+
import (
4+
"embed"
5+
)
6+
7+
//go:embed *.js *.css *.html
8+
var Assets embed.FS

graphql/internal/graphiql/graphiql.min.css

+337
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

graphql/internal/graphiql/graphiql.min.js

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

graphql/internal/graphiql/index.html

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!--
2+
* Copyright (c) 2021 GraphQL Contributors
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
-->
8+
<!DOCTYPE html>
9+
<html lang="en">
10+
<head>
11+
<title>GraphiQL</title>
12+
<style>
13+
body {
14+
height: 100%;
15+
margin: 0;
16+
width: 100%;
17+
overflow: hidden;
18+
}
19+
20+
#graphiql {
21+
height: 100vh;
22+
}
23+
</style>
24+
25+
<script src="/graphql/ui/react.production.min.js"></script>
26+
<script src="/graphql/ui/react-dom.production.min.js"></script>
27+
28+
<link rel="stylesheet" href="/graphql/ui/graphiql.min.css" />
29+
</head>
30+
31+
<body>
32+
<div id="graphiql">Loading...</div>
33+
<script src="/graphql/ui/graphiql.min.js" type="application/javascript"></script>
34+
<script>
35+
ReactDOM.render(
36+
React.createElement(GraphiQL, {
37+
fetcher: GraphiQL.createFetcher({
38+
url: '/graphql',
39+
}),
40+
defaultEditorToolsVisibility: true,
41+
}),
42+
document.getElementById('graphiql'),
43+
);
44+
</script>
45+
</body>
46+
</html>

0 commit comments

Comments
 (0)