Skip to content

Commit

Permalink
fix(gatsby-admin): Setup Gatsby Admin site (#23291)
Browse files Browse the repository at this point in the history
Co-Authored-By: LB <laurie@gatsbyjs.com>
Co-Authored-By: Brent Jackson <jxnblk@gmail.com>
  • Loading branch information
3 people committed Apr 24, 2020
1 parent ddf68db commit 385dfae
Show file tree
Hide file tree
Showing 14 changed files with 643 additions and 16 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
/packages/gatsby-plugin-mdx/ @gatsbyjs/themes-core
/packages/gatsby/src/bootstrap/load-themes @gatsbyjs/themes-core
/packages/gatsby-recipes/ @gatsbyjs/themes-core
/packages/gatsby-admin/ @gatsbyjs/themes-core
/packages/gatsby/src/internal-plugins/webpack-theme-component-shadowing/ @gatsbyjs/themes-core
18 changes: 18 additions & 0 deletions packages/gatsby-admin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Gatsby Admin

A visual interface to configure your Gatsby site.

We have not packaged this nicely yet, so it is not installable.

## How to develop it

However, you can do some manual set up in order to work with it locally. Follow these steps:

1. Navigate to the monorepo and open the `packages/gatsby-admin` directory.
2. In that directory, run `yarn develop`.
> If you see eslint errors you'll need to temporarily replace all references to `___loader` with `window.___loader` inside of `gatsby-link/index.js`.
3. In a new tab, navigate to a Gatsby site of your choice (or create one) that runs the latest version of Gatsby (recipes are a requirement).
4. From the `packages/gatsby-recipes/src` directory in the monorepo copy the `create-types.js` and `graphql.js` files. Use these files to replace those currently in your site's `node_modules/gatsby-recipes/src` directory.
5. Run `node ./node_modules/gatsby-recipes/src/graphql.js` to start the Recipes GraphQL server for that site.

You should now be able to visit `localhost:8000` to see Gatsby Admin for that site!
6 changes: 6 additions & 0 deletions packages/gatsby-admin/gatsby-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import Providers from './src/components/providers'

export const wrapPageElement = ({ element,props }) =>(
<Providers {...props}>{element}</Providers>
)
3 changes: 3 additions & 0 deletions packages/gatsby-admin/gatsby-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
plugins: [`gatsby-plugin-typescript`]
}
23 changes: 23 additions & 0 deletions packages/gatsby-admin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "gatsby-admin",
"version": "0.0.0",
"main": "index.js",
"author": "Max Stoiber",
"license": "MIT",
"private": true,
"dependencies": {
"@typescript-eslint/parser": "^2.28.0",
"@typescript-eslint/eslint-plugin": "^2.28.0",
"gatsby": "^2.20.25",
"gatsby-source-graphql": "^2.4.2",
"gatsby-plugin-typescript": "^2.3.3",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"subscriptions-transport-ws": "^0.9.16",
"typescript": "^3.8.3",
"urql": "^1.9.5"
},
"scripts": {
"develop": "gatsby develop"
}
}
7 changes: 7 additions & 0 deletions packages/gatsby-admin/src/components/providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react"
import { Provider } from "urql"
import client from "../urql-client"

export default ({ children }): React.ReactElement => (
<Provider value={client}>{children}</Provider>
)
148 changes: 148 additions & 0 deletions packages/gatsby-admin/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React from "react"
import { useQuery, useMutation } from "urql"

const InstallInput: React.FC<{}> = () => {
const [value, setValue] = React.useState(``)

const [, installGatbyPlugin] = useMutation(`
mutation installGatsbyPlugin($name: String!) {
createNpmPackage(npmPackage: {
name: $name,
dependencyType: "production"
}) {
id
name
}
createGatsbyPlugin(gatsbyPlugin: {
name: $name
}) {
id
name
}
}
`)

return (
<form
onSubmit={(evt): void => {
evt.preventDefault()
installGatbyPlugin({
name: value,
})
}}
>
<label>
Install:
<input
value={value}
onChange={(evt): void => setValue(evt.target.value)}
type="text"
placeholder="gatsby-plugin-cool"
/>
</label>
</form>
)
}

const DestroyButton: React.FC<{ name: string }> = ({ name }) => {
const [, deleteGatsbyPlugin] = useMutation(`
mutation destroyGatsbyPlugin($name: String!) {
destroyNpmPackage(npmPackage: {
name: $name,
id: $name,
dependencyType: "production"
}) {
id
name
}
destroyGatsbyPlugin(gatsbyPlugin: {
name: $name,
id: $name
}) {
id
name
}
}
`)

return (
<button
aria-label={`Delete ${name}`}
onClick={(): void => {
deleteGatsbyPlugin({ name })
}}
>
X
</button>
)
}

const Index: React.FC<{}> = () => {
const [{ data, fetching, error }] = useQuery({
query: `
{
allGatsbyPlugin {
nodes {
name
id
shadowedFiles
shadowableFiles
}
}
npmPackageJson(id: "name") {
name
value
}
}
`,
})

if (fetching) return <p>Loading...</p>

if (error) return <p>Oops something went wrong.</p>

return (
<>
<h1>{data.npmPackageJson.value.replace(/^"|"$/g, ``)}</h1>
<h2>Plugins</h2>
<ul>
{data.allGatsbyPlugin.nodes
.filter(plugin => plugin.name.indexOf(`gatsby-plugin`) === 0)
.map(plugin => (
<li key={plugin.id}>
{plugin.name} <DestroyButton name={plugin.name} />
</li>
))}
</ul>
<InstallInput />
<h2>Themes</h2>
<ul>
{data.allGatsbyPlugin.nodes
.filter(plugin => plugin.name.indexOf(`gatsby-theme`) === 0)
.map(plugin => (
<li key={plugin.id}>
<details>
<summary>
{plugin.name} <DestroyButton name={plugin.name} />
</summary>
<ul>
{plugin.shadowedFiles.map(file => (
<li key={file} style={{ opacity: 0.6 }}>
{file} (shadowed)
</li>
))}
{plugin.shadowableFiles.map(file => (
<li key={file}>{file}</li>
))}
</ul>
</details>
</li>
))}
</ul>

<InstallInput />
</>
)
}

export default Index
24 changes: 24 additions & 0 deletions packages/gatsby-admin/src/urql-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { createClient, defaultExchanges, subscriptionExchange } = require(`urql`)
const { SubscriptionClient } = require(`subscriptions-transport-ws`)

const subscriptionClient = new SubscriptionClient(
`ws://localhost:4000/graphql`,
{
reconnect: true,
}
)

const client = createClient({
fetch,
url: `http://localhost:4000/graphql`,
exchanges: [
...defaultExchanges,
subscriptionExchange({
forwardSubscription(operation) {
return subscriptionClient.request(operation)
},
}),
],
})

export default client
4 changes: 4 additions & 0 deletions packages/gatsby-admin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"strict": true
}
1 change: 1 addition & 0 deletions packages/gatsby-recipes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"gatsby-telemetry": "^1.2.6",
"glob": "^7.1.6",
"graphql": "^14.6.0",
"graphql-compose": "^6.3.8",
"graphql-subscriptions": "^1.1.0",
"graphql-type-json": "^0.3.1",
"html-tag-names": "^1.1.5",
Expand Down
92 changes: 79 additions & 13 deletions packages/gatsby-recipes/src/create-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const Joi2GQL = require(`./joi-to-graphql`)
const Joi = require(`@hapi/joi`)
const { GraphQLString, GraphQLObjectType, GraphQLList } = require(`graphql`)
const _ = require(`lodash`)
const { ObjectTypeComposer, schemaComposer } = require(`graphql-compose`)

const resources = require(`./resources`)

Expand All @@ -20,7 +21,8 @@ module.exports = () => {
return undefined
}

const types = []
const queryTypes = []
const mutationTypes = {}

const joiSchema = Joi.object().keys({
...resource.schema,
Expand All @@ -31,7 +33,8 @@ module.exports = () => {
name: resourceName,
})

const resourceType = {
// Query
const queryType = {
type,
args: {
id: { type: GraphQLString },
Expand All @@ -42,8 +45,9 @@ module.exports = () => {
},
}

types.push(resourceType)
queryTypes.push(queryType)

// Query connection
if (resource.all) {
const connectionTypeName = resourceName + `Connection`

Expand All @@ -62,20 +66,82 @@ module.exports = () => {
},
}

types.push(connectionType)
queryTypes.push(connectionType)
}

return types
// Destroy mutation
const camelCasedResourceName = _.camelCase(resourceName)
const inputType = ObjectTypeComposer.create(
type,
schemaComposer
).getInputType()

const destroyMutation = {
type,
args: {
[camelCasedResourceName]: { type: inputType },
},
resolve: async (_root, args, context) => {
const value = await resource.destroy(
context,
args[camelCasedResourceName]
)
return { ...value, _typeName: resourceName }
},
}

mutationTypes[`destroy${resourceName}`] = destroyMutation

// Create mutation
const createMutation = {
type,
args: {
[camelCasedResourceName]: { type: inputType },
},
resolve: (_root, args, context) =>
resource.create(context, args[camelCasedResourceName]),
}

mutationTypes[`create${resourceName}`] = createMutation

// Update mutation
const updateMutation = {
type,
args: {
[camelCasedResourceName]: { type: inputType },
},
resolve: (_root, args, context) =>
resource.update(context, args[camelCasedResourceName]),
}

mutationTypes[`update${resourceName}`] = updateMutation

return {
query: queryTypes,
mutation: mutationTypes,
}
}
)

const types = _.flatten(resourceTypes)
.filter(Boolean)
.reduce((acc, curr) => {
const typeName = typeNameToHumanName(curr.type.toString())
acc[typeName] = curr
return acc
}, {})
const queryTypes = _.flatten(
resourceTypes.filter(Boolean).map(r => r.query)
).reduce((acc, curr) => {
const typeName = typeNameToHumanName(curr.type.toString())
acc[typeName] = curr
return acc
}, {})

const mutationTypes = _.flatten(
resourceTypes.filter(Boolean).map(r => r.mutation)
).reduce((acc, curr) => {
Object.keys(curr).forEach(key => {
acc[typeNameToHumanName(key)] = curr[key]
})
return acc
}, {})

return types
return {
queryTypes,
mutationTypes,
}
}

0 comments on commit 385dfae

Please sign in to comment.