Skip to content

Commit 46aa56e

Browse files
acaoTallTed
andauthored
Installable PWA w/ simple server management plugin (#3380)
Co-authored-by: Ted Thibodeau Jr <[email protected]>
1 parent 7b00774 commit 46aa56e

File tree

15 files changed

+1890
-149
lines changed

15 files changed

+1890
-149
lines changed

Diff for: examples/graphiql-webpack/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
This example demonstrates how to transpile your own custom ES6 GraphiQL
44
implementation with webpack and babel configuration.
55

6+
It shows how to add plugins and even how to create a custom plugin.
7+
68
There is also a no-config example with `create-react-app`:
79

810
[![Edit graphiql-example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/graphiql-example-nhzvc)
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en" dir="ltr">
33
<head>
44
<meta charset="UTF-8" />
55
<meta
66
name="viewport"
77
content="width=device-width, initial-scale=1, shrink-to-fit=no"
88
/>
9+
<meta name="theme-color" content="#ffffff" />
10+
11+
<link rel="manifest" href="manifest.json" />
12+
<link rel="icon" href="logo.svg" />
913
<title>GraphiQL Webpack Example!</title>
1014
</head>
1115

1216
<body>
13-
<style>
14-
body {
15-
padding: 0;
16-
margin: 0;
17-
min-height: 100vh;
18-
}
19-
#root {
20-
height: 100vh;
21-
}
22-
</style>
2317
<div id="root"></div>
2418
</body>
2519
</html>

Diff for: examples/graphiql-webpack/package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
"description": "A GraphiQL example with webpack and typescript",
77
"scripts": {
88
"build-demo": "webpack-cli && mkdirp ../../packages/graphiql/webpack && cp -r dist/** ../../packages/graphiql/webpack",
9-
"start": "NODE_ENV=development webpack-dev-server"
9+
"start": "NODE_ENV=development webpack-cli serve"
1010
},
1111
"dependencies": {
1212
"@graphiql/plugin-code-exporter": "^0.3.4",
1313
"@graphiql/plugin-explorer": "^0.3.4",
1414
"@graphiql/toolkit": "^0.9.1",
15+
"@graphiql/react": "^0.19.3",
1516
"graphiql": "^3.0.5",
1617
"graphql": "^16.4.0",
1718
"graphql-ws": "^5.5.5",
@@ -24,6 +25,7 @@
2425
"@babel/preset-env": "^7.20.2",
2526
"@babel/preset-react": "^7.18.6",
2627
"babel-loader": "^9.1.2",
28+
"copy-webpack-plugin": "11.0.0",
2729
"cross-env": "^7.0.2",
2830
"css-loader": "^6.7.3",
2931
"html-webpack-plugin": "^5.5.0",
@@ -32,6 +34,8 @@
3234
"webpack": "5.76.0",
3335
"webpack-cli": "^5.0.1",
3436
"webpack-dev-server": "^4.11.1",
35-
"worker-loader": "^2.0.0"
37+
"worker-loader": "^2.0.0",
38+
"workbox-webpack-plugin": "^7.0.0",
39+
"webpack-manifest-plugin": "^5.0.0"
3640
}
3741
}

Diff for: examples/graphiql-webpack/public/logo.svg

+1
Loading

Diff for: examples/graphiql-webpack/src/constants.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const STARTING_URL =
2+
'https://swapi-graphql.netlify.app/.netlify/functions/index';
3+
4+
export const LAST_URL_KEY = 'lastURL';
5+
6+
export const PREV_URLS_KEY = 'previousURLs';

Diff for: examples/graphiql-webpack/src/index.css

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
body {
2+
padding: 0;
3+
margin: 0;
4+
min-height: 100vh;
5+
background-color: hsl(var(--color-base));
6+
}
7+
#root {
8+
height: 100vh;
9+
}
10+
11+
.plugin-title {
12+
font-size: var(--font-size-h2);
13+
font-weight: var(--font-weight-medium);
14+
display: flex;
15+
justify-content: space-between;
16+
align-items: center;
17+
padding-bottom: 1rem;
18+
}
19+
20+
.select-server--button {
21+
background-color: hsl(var(--color-primary));
22+
border: 0 none;
23+
display: block;
24+
width: 100%;
25+
color: hsl(var(--color-base));
26+
padding: 0.5rem;
27+
font-size: 1rem;
28+
margin-bottom: 1rem;
29+
}
30+
31+
.select-server--button.disabled {
32+
background-color: hsl(var(--color-base));
33+
color: hsl(var(--color-primary));
34+
}
35+
36+
.select-server--schema-error {
37+
color: hsl(var(--color-error));
38+
margin-top: 0.5rem;
39+
display: block;
40+
}
41+
42+
.select-server--schema-error code {
43+
padding: 0.5rem;
44+
background-color: hsla(var(--color-base), var(--alpha-secondary));
45+
}
46+
47+
.select-server--schema-success {
48+
color: hsl(var(--color-success));
49+
margin-top: 0.5rem;
50+
display: block;
51+
}
52+
53+
.select-server--schema-loading {
54+
color: hsl(var(--color-warning));
55+
margin-top: 0.5rem;
56+
display: block;
57+
}
58+
59+
input.select-server--input {
60+
display: block;
61+
width: 100%;
62+
color: hsla(var(--color-neutral), var(--alpha-secondary));
63+
padding: 0.5rem;
64+
background-color: hsla(var(--color-primary), var(--alpha-background-medium));
65+
border: 0 none;
66+
font-size: 1.05rem;
67+
}
68+
69+
.select-server--input-error {
70+
color: hsl(var(--color-error));
71+
margin-top: 0.5rem;
72+
display: block;
73+
}
74+
75+
li.select-server--previous-entry {
76+
display: flex;
77+
flex-direction: row;
78+
flex-grow: inherit;
79+
width: 100%;
80+
}
81+
82+
li.select-server--previous-entry span {
83+
display: flex;
84+
cursor: pointer;
85+
padding: 0.5rem;
86+
text-overflow: ellipsis;
87+
white-space: nowrap;
88+
overflow: hidden;
89+
flex-grow: 1;
90+
}
91+
92+
li.select-server--previous-entry span:hover,
93+
li.select-server--previous-entry button:hover {
94+
background-color: hsl(var(--color-primary));
95+
color: hsl(var(--color-base));
96+
}
97+
98+
li.select-server--previous-entry button {
99+
background-color: transparent;
100+
border: hsla(var(--color-neutral), 1);
101+
display: flex;
102+
color: hsla(var(--color-neutral), 1);
103+
font-size: 1rem;
104+
cursor: pinter;
105+
padding: 0.5rem;
106+
}

Diff for: examples/graphiql-webpack/src/index.jsx

+62-21
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,53 @@ import * as React from 'react';
33
import { createRoot } from 'react-dom/client';
44
import { GraphiQL } from 'graphiql';
55
import { explorerPlugin } from '@graphiql/plugin-explorer';
6-
import { snippets } from './snippets';
6+
import { getSnippets } from './snippets';
77
import { codeExporterPlugin } from '@graphiql/plugin-code-exporter';
88
import 'graphiql/graphiql.css';
99
import '@graphiql/plugin-explorer/dist/style.css';
1010
import '@graphiql/plugin-code-exporter/dist/style.css';
11+
import { createGraphiQLFetcher } from '@graphiql/toolkit';
12+
import { useStorageContext } from '@graphiql/react';
13+
14+
export const STARTING_URL =
15+
'https://swapi-graphql.netlify.app/.netlify/functions/index';
16+
17+
import './index.css';
18+
import { serverSelectPlugin, LAST_URL_KEY } from './select-server-plugin';
19+
20+
if ('serviceWorker' in navigator) {
21+
window.addEventListener('load', () => {
22+
navigator.serviceWorker
23+
.register('/service-worker.js')
24+
.then(registration => {
25+
console.log('SW registered: ', registration);
26+
})
27+
.catch(registrationError => {
28+
console.log('SW registration failed: ', registrationError);
29+
});
30+
});
31+
}
1132

1233
/**
13-
* A manual fetcher implementation, you should probably
14-
* just use `createGraphiQLFetcher` from `@graphiql/toolkit
34+
* A manual fetcher implementation example
1535
* @returns
1636
*/
17-
const fetcher = async (graphQLParams, options) => {
18-
const data = await fetch(
19-
'https://swapi-graphql.netlify.app/.netlify/functions/index',
20-
{
21-
method: 'POST',
22-
headers: {
23-
Accept: 'application/json',
24-
'Content-Type': 'application/json',
25-
...options.headers,
26-
},
27-
body: JSON.stringify(graphQLParams),
28-
credentials: 'same-origin',
29-
},
30-
);
31-
return data.json().catch(() => data.text());
32-
};
37+
// const fetcher = async (graphQLParams, options) => {
38+
// const data = await fetch(
39+
// STARTING_URL,
40+
// {
41+
// method: 'POST',
42+
// headers: {
43+
// Accept: 'application/json',
44+
// 'Content-Type': 'application/json',
45+
// ...options.headers,
46+
// },
47+
// body: JSON.stringify(graphQLParams),
48+
// credentials: 'same-origin',
49+
// },
50+
// );
51+
// return data.json().catch(() => data.text());
52+
// };
3353

3454
const style = { height: '100vh' };
3555
/**
@@ -38,15 +58,36 @@ const style = { height: '100vh' };
3858
* then use the `useMemo` hook
3959
*/
4060
const explorer = explorerPlugin();
41-
const exporter = codeExporterPlugin({ snippets });
4261

4362
const App = () => {
63+
const storage = useStorageContext();
64+
65+
const lastUrl = storage?.get(LAST_URL_KEY);
66+
const [currentUrl, setUrl] = React.useState(lastUrl ?? STARTING_URL);
67+
// TODO: a breaking change where we make URL an internal state concern, and then expose hooks
68+
// so that you can handle/set URL state internally from a plugin
69+
// fetcher could then pass a dynamic URL config object to the fetcher internally
70+
const exporter = React.useMemo(
71+
() =>
72+
codeExporterPlugin({ snippets: getSnippets({ serverUrl: currentUrl }) }),
73+
[currentUrl],
74+
);
75+
const fetcher = React.useMemo(
76+
() => createGraphiQLFetcher({ url: currentUrl }),
77+
[currentUrl],
78+
);
79+
const serverSelect = React.useMemo(
80+
() => serverSelectPlugin({ url: currentUrl, setUrl }),
81+
[currentUrl],
82+
);
83+
4484
return (
4585
<GraphiQL
4686
style={style}
4787
// eslint-disable-next-line @arthurgeron/react-usememo/require-usememo
48-
plugins={[explorer, exporter]}
88+
plugins={[serverSelect, explorer, exporter]}
4989
fetcher={fetcher}
90+
shouldPersistHeaders
5091
/>
5192
);
5293
};

0 commit comments

Comments
 (0)