Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gatsby-admin): Setup Gatsby Admin site #23291

Merged
merged 26 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
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.
LekoArts marked this conversation as resolved.
Show resolved Hide resolved

## 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>
)
147 changes: 147 additions & 0 deletions packages/gatsby-admin/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
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
LekoArts marked this conversation as resolved.
Show resolved Hide resolved
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.5",
"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,
}
}
Loading