add react graphql tutorial (#2061)
@ -1,10 +1,11 @@
|
||||
# Community
|
||||
|
||||
This directory contains community-contributed code and content that supplements Hasura GraphQL Engine. It includes several example applications built using GraphQL Engine to demonstrate its features, several boilerplates for users to get started with auth webhooks, triggers, etc. and some community tooling around the Engine.
|
||||
This directory contains community-contributed code and content that supplements Hasura GraphQL Engine. It includes several example applications built using GraphQL Engine to demonstrate its features, several boilerplates for users to get started with auth webhooks, triggers, etc. and some community tooling around the Engine. There are also graphql tutorials built using GraphQL Engine as the backend. These tutorials help in learning how to integrate GraphQL APIs with your favourite web/mobile framework.
|
||||
|
||||
The following categories of community content can be found in their respective sub-folders:
|
||||
|
||||
- [Boilerplates](boilerplates)
|
||||
- [Learn](learn)
|
||||
- [Sample Apps](sample-apps)
|
||||
- [Tools](tools)
|
||||
|
||||
|
1
community/learn/graphql-tutorials/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
remote-hasura.yaml
|
5
community/learn/graphql-tutorials/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# GraphQL Tutorials
|
||||
|
||||
Visit [learn.hasura.io](https://learn.hasura.io) to learn how to integrate GraphQL APIs with your favourite web/mobile framework in a real world scenario.
|
||||
|
||||
_This directory contains all the source code the powers GraphQL Turtorials_
|
@ -0,0 +1,39 @@
|
||||
const { query } = require('graphqurl@0.3.2');
|
||||
module.exports = function (user, context, cb) {
|
||||
// Perform any asynchronous actions, e.g. send notification to Slack.
|
||||
let userIdPrefix = 'auth0|';
|
||||
if (context.connection.name !== 'Username-Password-Authentication') {
|
||||
userIdPrefix = 'google-oauth2|';
|
||||
}
|
||||
query(
|
||||
{
|
||||
query: `
|
||||
mutation($userId: String!, $nickname: String) {
|
||||
insert_users(
|
||||
objects: [{ auth0_id: $userId, name: $nickname }]
|
||||
on_conflict: {
|
||||
constraint: users_pkey
|
||||
update_columns: [last_seen, name]
|
||||
}
|
||||
) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`,
|
||||
endpoint: 'https://learn.hasura.io/graphql',
|
||||
headers: {
|
||||
'x-hasura-admin-secret': '<replace-with-admin-secret>'
|
||||
},
|
||||
variables: {
|
||||
userId: userIdPrefix + user.id,
|
||||
nickname: user.email.split('@')[0]
|
||||
}
|
||||
}
|
||||
).then((response) => {
|
||||
cb();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
cb();
|
||||
});
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
function (user, context, callback) {
|
||||
const userId = user.user_id;
|
||||
const nickname = user.nickname;
|
||||
|
||||
request.post({
|
||||
headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': ''},
|
||||
url: 'https://learn.hasura.io/graphql',
|
||||
body: `{\"query\":\"mutation($userId: String!, $nickname: String) {\\n insert_users(\\n objects: [{ auth0_id: $userId, name: $nickname }]\\n on_conflict: {\\n constraint: users_pkey\\n update_columns: [last_seen, name]\\n }\\n ) {\\n affected_rows\\n }\\n }\",\"variables\":{\"userId\":\"${userId}\",\"nickname\":\"${nickname}\"}}`
|
||||
}, function(error, response, body){
|
||||
console.log(body);
|
||||
callback(null, user, context);
|
||||
});
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["stage-0", "env", "react"],
|
||||
"plugins": ["transform-class-properties", "transform-react-remove-prop-types", "istanbul"]
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
---
|
||||
# Output debugging info
|
||||
# loglevel: debug
|
||||
|
||||
# Major version of Bootstrap: 3 or 4
|
||||
bootstrapVersion: 3
|
||||
|
||||
# If Bootstrap version 3 is used - turn on/off custom icon font path
|
||||
useCustomIconFontPath: false
|
||||
|
||||
# Webpack loaders, order matters
|
||||
styleLoaders:
|
||||
- style
|
||||
- css
|
||||
- sass
|
||||
|
||||
# Extract styles to stand-alone css file
|
||||
# Different settings for different environments can be used,
|
||||
# It depends on value of NODE_ENV environment variable
|
||||
# This param can also be set in webpack config:
|
||||
# entry: 'bootstrap-loader/extractStyles'
|
||||
#extractStyles: false
|
||||
env:
|
||||
development:
|
||||
extractStyles: false
|
||||
production:
|
||||
extractStyles: true
|
||||
|
||||
|
||||
# Customize Bootstrap variables that get imported before the original Bootstrap variables.
|
||||
# Thus, derived Bootstrap variables can depend on values from here.
|
||||
# See the Bootstrap _variables.scss file for examples of derived Bootstrap variables.
|
||||
#
|
||||
|
||||
preBootstrapCustomizations: ./src/theme/variables.scss
|
||||
|
||||
|
||||
# This gets loaded after bootstrap/variables is loaded
|
||||
# Thus, you may customize Bootstrap variables
|
||||
# based on the values established in the Bootstrap _variables.scss file
|
||||
#
|
||||
|
||||
bootstrapCustomizations: ./src/theme/bootstrap.overrides.scss
|
||||
|
||||
|
||||
# Import your custom styles here
|
||||
# Usually this endpoint-file contains list of @imports of your application styles
|
||||
#
|
||||
# appStyles: ./path/to/your/app/styles/endpoint.scss
|
||||
|
||||
|
||||
### Bootstrap styles
|
||||
styles:
|
||||
|
||||
# Mixins
|
||||
mixins: true
|
||||
|
||||
# Reset and dependencies
|
||||
normalize: true
|
||||
print: true
|
||||
glyphicons: true
|
||||
|
||||
# Core CSS
|
||||
scaffolding: true
|
||||
type: true
|
||||
code: true
|
||||
grid: true
|
||||
tables: true
|
||||
forms: true
|
||||
buttons: true
|
||||
|
||||
# Components
|
||||
component-animations: true
|
||||
dropdowns: true
|
||||
button-groups: true
|
||||
input-groups: true
|
||||
navs: true
|
||||
navbar: true
|
||||
breadcrumbs: true
|
||||
pagination: true
|
||||
pager: true
|
||||
labels: true
|
||||
badges: true
|
||||
jumbotron: true
|
||||
thumbnails: true
|
||||
alerts: true
|
||||
progress-bars: true
|
||||
media: true
|
||||
list-group: true
|
||||
panels: true
|
||||
wells: true
|
||||
responsive-embed: true
|
||||
close: true
|
||||
|
||||
# Components w/ JavaScript
|
||||
modals: true
|
||||
tooltip: true
|
||||
popovers: true
|
||||
carousel: true
|
||||
|
||||
# Utility classes
|
||||
utilities: true
|
||||
responsive-utilities: true
|
||||
|
||||
### Bootstrap scripts
|
||||
#scripts: false
|
||||
|
||||
scripts:
|
||||
transition: false
|
||||
alert: false
|
||||
button: true
|
||||
carousel: false
|
||||
collapse: false
|
||||
dropdown: true
|
||||
modal: true
|
||||
tooltip: false
|
||||
popover: false
|
||||
scrollspy: false
|
||||
tab: false
|
||||
affix: false
|
@ -0,0 +1 @@
|
||||
node_modules
|
@ -0,0 +1,2 @@
|
||||
webpack/*
|
||||
src/utils.js
|
@ -0,0 +1,101 @@
|
||||
{ "extends": "eslint-config-airbnb",
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"mocha": true,
|
||||
"cypress/globals": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"allowForLoopAfterthoughts": true,
|
||||
"react/no-multi-comp": 0,
|
||||
"import/default": 0,
|
||||
"import/no-duplicates": 0,
|
||||
"import/named": 0,
|
||||
"import/first": 0,
|
||||
"import/namespace": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-named-as-default": 2,
|
||||
"import/extensions": 0,
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"comma-dangle": 0,
|
||||
"id-length": [1, {"min": 1, "properties": "never"}],
|
||||
"indent": [2, 2, {"SwitchCase": 1}],
|
||||
"no-console": 0,
|
||||
"arrow-parens": 0,
|
||||
"no-alert": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-unsafe-negation": 0,
|
||||
"no-loop-func": 0,
|
||||
"no-lonely-if": 0,
|
||||
"no-bitwise": 0,
|
||||
"global-require": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-useless-return": 0,
|
||||
"no-restricted-syntax": 0,
|
||||
"no-prototype-builtins": 0,
|
||||
"array-callback-return": 0,
|
||||
"no-useless-concat": 0,
|
||||
"consistent-return": 0,
|
||||
"class-methods-use-this": 0,
|
||||
"arrow-body-style": 0,
|
||||
"prefer-template": 0,
|
||||
"prefer-spread": 0,
|
||||
"object-shorthand": 0,
|
||||
"object-curly-newline": 0,
|
||||
"spaced-comment": 0,
|
||||
"prefer-destructuring": ["error", {"object": false, "array": false}],
|
||||
"prefer-rest-params": 0,
|
||||
"function-paren-newline": 0,
|
||||
"no-case-declarations": 0,
|
||||
"no-restricted-globals": 0,
|
||||
"no-unneeded-ternary": 0,
|
||||
"no-mixed-operators": 0,
|
||||
"no-return-assign": 0,
|
||||
"operator-assignment": 0,
|
||||
"strict": 0,
|
||||
"react/jsx-no-duplicate-props": 0,
|
||||
"react/jsx-filename-extension": 0,
|
||||
"react/jsx-curly-brace-presence": 0,
|
||||
"react/forbid-prop-types": 0,
|
||||
"react/require-default-props": 0,
|
||||
"react/no-unused-prop-types": 0,
|
||||
"react/no-string-refs": 0,
|
||||
"react/no-unused-state": 0,
|
||||
"react/no-array-index-key": 0,
|
||||
"react/jsx-no-bind": 0,
|
||||
"react/prop-types": 0,
|
||||
"react/prefer-stateless-function": 0,
|
||||
"react/no-unescaped-entities": 0,
|
||||
"jsx-a11y/click-events-have-key-events": 0,
|
||||
"jsx-a11y/no-static-element-interactions": 0,
|
||||
"jsx-a11y/no-noninteractive-element-interactions": 0,
|
||||
"jsx-a11y/label-has-for": 0,
|
||||
"jsx-a11y/anchor-is-valid": 0,
|
||||
"jsx-a11y/lang": 0,
|
||||
"jsx-a11y/alt-text": 0,
|
||||
"max-len": 0
|
||||
},
|
||||
"plugins": [
|
||||
"react", "import", "cypress"
|
||||
],
|
||||
|
||||
"settings": {
|
||||
"import/parser": "babel-eslint",
|
||||
"parser": "babel-esling",
|
||||
"import/resolve": {
|
||||
"moduleDirectory": ["node_modules", "src"]
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"__DEVELOPMENT__": true,
|
||||
"__CLIENT__": true,
|
||||
"__SERVER__": true,
|
||||
"__DISABLE_SSR__": true,
|
||||
"__DEVTOOLS__": true,
|
||||
"socket": true,
|
||||
"webpackIsomorphicTools": true
|
||||
}
|
||||
}
|
12
community/learn/graphql-tutorials/backend-services/graphiql/.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
.env
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
.nyc_output
|
||||
node_modules
|
||||
static/dist
|
||||
webpack-assets.json
|
||||
webpack-stats.json
|
||||
npm-debug.log
|
||||
*.swp
|
||||
coverage
|
||||
.idea/*
|
@ -0,0 +1,5 @@
|
||||
*
|
||||
**/*
|
||||
!static/dist/main.js
|
||||
!static/dist/vendor.js
|
||||
!static/dist/main.css
|
@ -0,0 +1,23 @@
|
||||
FROM node:8.9-alpine
|
||||
|
||||
# Create app directory
|
||||
ENV GRAPHQL_ENDPOINT=https://learn.hasura.io/graphql
|
||||
ENV REACT_APP_CALLBACK_URL=https://learn.hasura.io/graphql/graphiql/callback
|
||||
WORKDIR /app
|
||||
|
||||
# Install app dependencies
|
||||
RUN npm config set unsafe-perm true
|
||||
RUN npm -g install serve
|
||||
# A wildcard is used to ensure both package.json AND package-lock.json are copied
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . /app
|
||||
#Build react/vue/angular bundle static files
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 8080
|
||||
# serve dist folder on port 8080
|
||||
CMD ["serve", "-s", "static", "-p", "8080"]
|
@ -0,0 +1,32 @@
|
||||
## GraphiQL
|
||||
|
||||
This version of GraphiQL is a fork of the original version with a simple header management UI.
|
||||
|
||||
You can access it live here - https://learn.hasura.io/graphql/graphiql
|
||||
|
||||
## Usage of Environment Variables
|
||||
|
||||
This app uses a few environment variables which are required for development. The production build uses values directly present in index.html serving this app.
|
||||
|
||||
We use [dotenv](https://github.com/motdotla/dotenv) for setting environment variables for development. Create a `.env` file in the root directory (wherever package.json is) and set the following values. Replace accordingly for testing.
|
||||
|
||||
```
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
GRAPHQL_ENDPOINT=http://localhost:8090/v1alpha1/graphql
|
||||
HEADER_STRING='{}'
|
||||
VARIABLE_STRING='{}'
|
||||
QUERY_STRING='query { test_table { id } }'
|
||||
REACT_APP_CALLBACK_URL='http://localhost:3000/callback'
|
||||
```
|
||||
|
||||
**Note**
|
||||
The .env file should not be in version control.
|
||||
|
||||
## Deployment
|
||||
|
||||
```
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
The static assets will be generated in `static` folder. There is an index.html file referencing the css and js assets inside `dist` folder.
|
@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
hmrPort: parseInt(process.env.PORT, 10) + 1 || 3001,
|
||||
hmrHost: process.env.HOST || '127.0.0.1',
|
||||
appHost: '0.0.0.0',
|
||||
port: { development: process.env.PORT, production: 8080 },
|
||||
assetsPrefix: '/rstatic',
|
||||
webpackPrefix: '/rstatic/dist/',
|
||||
appPrefix: '/rapp',
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
// enable runtime transpilation to use ES6/7 in node
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const babelrc = fs.readFileSync('.babelrc');
|
||||
let config;
|
||||
|
||||
try {
|
||||
config = JSON.parse(babelrc);
|
||||
} catch (err) {
|
||||
console.error('==> ERROR: Error parsing your .babelrc.');
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
require('babel-core/register')(config);
|
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
require('./server.babel'); // babel registration (runtime transpilation for node)
|
||||
const path = require('path');
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..');
|
||||
/**
|
||||
* Define isomorphic constants.
|
||||
*/
|
||||
global.__CLIENT__ = false;
|
||||
global.__SERVER__ = true;
|
||||
global.__DISABLE_SSR__ = false; // <----- DISABLES SERVER SIDE RENDERING FOR ERROR DEBUGGING
|
||||
global.__DEVELOPMENT__ = process.env.NODE_ENV !== 'production';
|
||||
|
||||
if (__DEVELOPMENT__) {
|
||||
if (
|
||||
!require('piping')({
|
||||
//Fork the process and supervise the child for hot-reloading code
|
||||
hook: true,
|
||||
ignore: /(\/\.|~$|\.json|\.scss$)/i,
|
||||
})
|
||||
) {
|
||||
return; //The parent process ends, and child process continues from below
|
||||
}
|
||||
}
|
||||
|
||||
const WebpackIsomorphicTools = require('webpack-isomorphic-tools');
|
||||
global.webpackIsomorphicTools = new WebpackIsomorphicTools(
|
||||
require('../webpack/webpack-isomorphic-tools')
|
||||
).server(rootDir, () => {
|
||||
require('../src/server');
|
||||
});
|
||||
|
||||
require('../src/server');
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "graphqurl-graphiql",
|
||||
"alias": "hasura-graphiql.now.sh"
|
||||
}
|
16085
community/learn/graphql-tutorials/backend-services/graphiql/package-lock.json
generated
Normal file
@ -0,0 +1,143 @@
|
||||
{
|
||||
"name": "graphql-tutorials-graphiql",
|
||||
"description": "Explore GraphQL APIs with headers",
|
||||
"author": "Hasura (https://github.com/hasura/graphql-engine)",
|
||||
"license": "MIT",
|
||||
"version": "0.1.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hasura/graphql-engine"
|
||||
},
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"start": "concurrently --kill-others \"npm run start-prod\"",
|
||||
"start-prod": "better-npm-run start-prod",
|
||||
"build": "webpack --progress -p --colors --display-error-details --config webpack/prod.config.js",
|
||||
"build-unused": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js --json | webpack-unused -s src",
|
||||
"lint": "eslint -c .eslintrc src api",
|
||||
"start-dev": "better-npm-run start-dev",
|
||||
"watch-client": "better-npm-run watch-client",
|
||||
"dev": "concurrently --kill-others \"npm run watch-client\" \"npm run start-dev\" ",
|
||||
"cypress": "cypress open",
|
||||
"test": "cypress run --spec 'cypress/integration/test_complete.js' --config baseUrl=$CYPRESS_BASE_URL"
|
||||
},
|
||||
"betterScripts": {
|
||||
"start-prod": {
|
||||
"command": "node ./bin/server.js",
|
||||
"env": {
|
||||
"NODE_PATH": "./src",
|
||||
"NODE_ENV": "production",
|
||||
"PORT": 8080
|
||||
}
|
||||
},
|
||||
"start-dev": {
|
||||
"command": "node -r dotenv/config ./bin/server.js"
|
||||
},
|
||||
"watch-client": {
|
||||
"command": "node -r dotenv/config webpack/webpack-dev-server.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"apollo-link": "^1.2.2",
|
||||
"apollo-link-ws": "^1.0.8",
|
||||
"auth0-js": "^9.8.2",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"deep-equal": "^1.0.1",
|
||||
"graphiql": "^0.11.11",
|
||||
"graphiql-explorer": "^0.3.7",
|
||||
"graphql": "^14.0.2",
|
||||
"hasura-console-graphiql": "0.0.1",
|
||||
"history": "^3.0.0",
|
||||
"hoist-non-react-statics": "^1.0.3",
|
||||
"invariant": "^2.2.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"less": "^3.7.1",
|
||||
"lru-memoize": "^1.0.0",
|
||||
"map-props": "^1.0.0",
|
||||
"multireducer": "^1.0.2",
|
||||
"piping": "^0.3.0",
|
||||
"prettier": "^1.13.0",
|
||||
"pretty-error": "^1.2.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"query-string": "^6.1.0",
|
||||
"react": "^16.4.0",
|
||||
"react-dom": "^16.4.0",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-hot-loader": "^4.2.0",
|
||||
"react-progress-bar-plus": "^1.3.1",
|
||||
"react-redux": "^5.0.6",
|
||||
"react-router": "^3.2.0",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"redux": "^4.0.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"subscriptions-transport-ws": "^0.9.12",
|
||||
"valid-url": "^1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-istanbul": "^4.1.6",
|
||||
"babel-plugin-react-transform": "~3.0.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.10",
|
||||
"babel-plugin-typecheck": "^2.0.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-runtime": "~6.26.0",
|
||||
"better-npm-run": "^0.1.0",
|
||||
"bootstrap-loader": "^2.2.0",
|
||||
"bootstrap-sass": "^3.3.7",
|
||||
"clean-webpack-plugin": "^0.1.17",
|
||||
"concurrently": "^3.5.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"dotenv": "^5.0.1",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-airbnb": "16.1.0",
|
||||
"eslint-loader": "^1.0.0",
|
||||
"eslint-plugin-chai-friendly": "^0.4.1",
|
||||
"eslint-plugin-cypress": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.12.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||
"eslint-plugin-react": "^7.9.1",
|
||||
"express": "^4.13.3",
|
||||
"express-session": "^1.12.1",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^1.1.11",
|
||||
"font-awesome": "^4.7.0",
|
||||
"font-awesome-webpack": "0.0.4",
|
||||
"husky": "^0.14.3",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"jquery": "^3.3.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"less-loader": "^4.1.0",
|
||||
"lint-staged": "^6.1.1",
|
||||
"mini-css-extract-plugin": "^0.4.0",
|
||||
"node-sass": "^4.9.2",
|
||||
"nyc": "^12.0.2",
|
||||
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
||||
"react-a11y": "^0.2.6",
|
||||
"react-addons-test-utils": "^15.0.3",
|
||||
"react-transform-hmr": "1.0.4",
|
||||
"redux-devtools": "^3.4.1",
|
||||
"redux-devtools-dock-monitor": "^1.1.2",
|
||||
"redux-devtools-log-monitor": "^1.3.0",
|
||||
"resolve-url-loader": "^2.3.0",
|
||||
"sass-loader": "^7.0.1",
|
||||
"sinon": "^1.17.7",
|
||||
"style-loader": "^0.20.3",
|
||||
"timekeeper": "1.0.0",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||
"unused-files-webpack-plugin": "^3.4.0",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "^4.14.0",
|
||||
"webpack-cli": "^3.0.8",
|
||||
"webpack-dev-middleware": "^3.1.3",
|
||||
"webpack-hot-middleware": "^2.22.2",
|
||||
"webpack-isomorphic-tools": "^3.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.1"
|
||||
}
|
||||
}
|
94
community/learn/graphql-tutorials/backend-services/graphiql/src/client.js
Executable file
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* THIS IS THE ENTRY POINT FOR THE CLIENT, JUST LIKE server.js IS THE ENTRY POINT FOR THE SERVER.
|
||||
*/
|
||||
// import 'babel-polyfill';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createLogger } from 'redux-logger';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { Router, browserHistory } from 'react-router';
|
||||
import { routerMiddleware, syncHistoryWithStore } from 'react-router-redux';
|
||||
import { compose, createStore, applyMiddleware } from 'redux';
|
||||
import { useBasename } from 'history';
|
||||
|
||||
import getRoutes from './routes';
|
||||
|
||||
import reducer from './reducer';
|
||||
|
||||
// Create the store
|
||||
let _finalCreateStore;
|
||||
|
||||
if (__DEVELOPMENT__) {
|
||||
_finalCreateStore = compose(
|
||||
applyMiddleware(thunk, routerMiddleware(browserHistory), createLogger()),
|
||||
require('redux-devtools').persistState(
|
||||
window.location.href.match(/[?&]debug_session=([^&]+)\b/)
|
||||
)
|
||||
)(createStore);
|
||||
} else {
|
||||
_finalCreateStore = compose(
|
||||
applyMiddleware(thunk, routerMiddleware(browserHistory))
|
||||
)(createStore);
|
||||
}
|
||||
|
||||
const hashLinkScroll = () => {
|
||||
const { hash } = window.location;
|
||||
if (hash !== '') {
|
||||
// Push onto callback queue so it runs after the DOM is updated,
|
||||
// this is required when navigating from a different page so that
|
||||
// the element is rendered on the page before trying to getElementById.
|
||||
setTimeout(() => {
|
||||
const id = hash.replace('#', '');
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.scrollIntoView();
|
||||
}
|
||||
}, 0);
|
||||
} else {
|
||||
// This is a hack to solve the issue with scroll retention during page change.
|
||||
setTimeout(() => {
|
||||
const element = document.getElementsByTagName('body');
|
||||
if (element && element.length > 0) {
|
||||
element[0].scrollIntoView();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const store = _finalCreateStore(reducer);
|
||||
const history = syncHistoryWithStore(browserHistory, store);
|
||||
|
||||
/* ****************************************************************** */
|
||||
|
||||
// Enable hot reloading
|
||||
if (__DEVELOPMENT__ && module.hot) {
|
||||
module.hot.accept('./reducer', () => {
|
||||
store.replaceReducer(require('./reducer'));
|
||||
});
|
||||
}
|
||||
|
||||
// Main routes and rendering
|
||||
const main = (
|
||||
<Router
|
||||
history={useBasename(() => history)({ basename: '/graphql/graphiql' })}
|
||||
// history={useBasename(() => history)({ basename: '/' })}
|
||||
routes={getRoutes(store)}
|
||||
onUpdate={hashLinkScroll}
|
||||
/>
|
||||
);
|
||||
|
||||
const dest = document.getElementById('content');
|
||||
ReactDOM.render(
|
||||
<Provider store={store} key="provider">
|
||||
{main}
|
||||
</Provider>,
|
||||
dest
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
window.React = React; // enable debugger
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
class PageNotFound extends Component {
|
||||
render() {
|
||||
const styles = require('./Styles.scss');
|
||||
return (
|
||||
<div className={styles.viewcontainer}>
|
||||
<Helmet title="404 - Page Not Found" />
|
||||
<div className={'container ' + styles.centerContent}>
|
||||
<div className={'row ' + styles.message}>
|
||||
<div className="col-xs-8">
|
||||
<h1>404</h1>
|
||||
This page doesn't exist.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PageNotFound.propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default connect()(PageNotFound);
|
@ -0,0 +1,42 @@
|
||||
.container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.viewcontainer {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: table;
|
||||
|
||||
.centerContent{
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
|
||||
.message {
|
||||
padding: 50px 20%;
|
||||
}
|
||||
.message h1 {
|
||||
font-size: 54px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.message p {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.message p > a {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #eee;
|
||||
h2 {
|
||||
margin: 0;
|
||||
padding: 26px;
|
||||
float: left;
|
||||
line-height: 26px;
|
||||
}
|
||||
.nav {
|
||||
padding: 20px;
|
||||
float: left;
|
||||
}
|
||||
}
|
@ -0,0 +1,247 @@
|
||||
import defaultState from './state';
|
||||
// import fetch from 'isomorphic-fetch';
|
||||
|
||||
import { SubscriptionClient } from 'subscriptions-transport-ws';
|
||||
import { WebSocketLink } from 'apollo-link-ws';
|
||||
import { parse } from 'graphql';
|
||||
import { execute } from 'apollo-link';
|
||||
import { push } from 'react-router-redux';
|
||||
|
||||
const REQUEST_HEADER_CHANGED = 'ApiExplorer/REQUEST_HEADER_CHANGED';
|
||||
const REQUEST_HEADER_ADDED = 'ApiExplorer/REQUEST_HEADER_ADDED';
|
||||
const REQUEST_HEADER_REMOVED = 'ApiExplorer/REQUEST_HEADER_REMOVED';
|
||||
|
||||
const FOCUS_ROLE_HEADER = 'ApiExplorer/FOCUS_ROLE_HEADER';
|
||||
const UNFOCUS_ROLE_HEADER = 'ApiExplorer/UNFOCUS_ROLE_HEADER';
|
||||
const GRAPHQL_ENDPOINT_CHANGED = 'ApiExplorer/GRAPHQL_ENDPOINT_CHANGED';
|
||||
|
||||
import { getHeadersAsJSON } from './utils';
|
||||
|
||||
const focusHeaderTextbox = () => ({ type: FOCUS_ROLE_HEADER });
|
||||
const unfocusTypingHeader = () => ({ type: UNFOCUS_ROLE_HEADER });
|
||||
|
||||
const updateGraphQLEndpoint = (endpoint) => {
|
||||
return (dispatch) => {
|
||||
dispatch({ type: GRAPHQL_ENDPOINT_CHANGED, data: endpoint });
|
||||
// set local storage
|
||||
window.localStorage.setItem('ONLINE_GRAPHIQL_ENDPOINT', endpoint);
|
||||
dispatch(push('/graphiql'));
|
||||
};
|
||||
};
|
||||
|
||||
const getRemoteQueries = (queryUrl, cb) => {
|
||||
fetch(queryUrl)
|
||||
.then(resp => resp.text().then(cb))
|
||||
.catch(e => console.log('Invalid query URL: ', e));
|
||||
};
|
||||
|
||||
const createWsClient = (url, headers) => {
|
||||
const gqlUrl = new URL(url);
|
||||
let websocketProtocol = 'ws';
|
||||
if (gqlUrl.protocol === 'https:') {
|
||||
websocketProtocol = 'wss';
|
||||
}
|
||||
const headersFinal = getHeadersAsJSON(headers);
|
||||
const graphqlUrl = `${websocketProtocol}://${url.split('//')[1]}`;
|
||||
const client = new SubscriptionClient(graphqlUrl, {
|
||||
connectionParams: {
|
||||
headers: {
|
||||
...headersFinal,
|
||||
},
|
||||
},
|
||||
reconnect: true,
|
||||
});
|
||||
return client;
|
||||
};
|
||||
|
||||
const graphqlSubscriber = (graphQLParams, url, headers) => {
|
||||
const link = new WebSocketLink(createWsClient(url, headers));
|
||||
try {
|
||||
const fetcher = operation => {
|
||||
operation.query = parse(operation.query);
|
||||
return execute(link, operation);
|
||||
};
|
||||
return fetcher(graphQLParams);
|
||||
} catch (e) {
|
||||
return e.json();
|
||||
}
|
||||
};
|
||||
|
||||
const isSubscription = graphQlParams => {
|
||||
const queryDoc = parse(graphQlParams.query);
|
||||
for (const definition of queryDoc.definitions) {
|
||||
if (definition.kind === 'OperationDefinition') {
|
||||
const operation = definition.operation;
|
||||
if (operation === 'subscription') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const graphQLFetcherFinal = (graphQLParams, url, headers) => {
|
||||
if (isSubscription(graphQLParams)) {
|
||||
return graphqlSubscriber(graphQLParams, url, headers);
|
||||
}
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: getHeadersAsJSON(headers),
|
||||
body: JSON.stringify(graphQLParams),
|
||||
}).then(response => response.json());
|
||||
};
|
||||
|
||||
const changeRequestHeader = (index, key, newValue, isDisabled) => ({
|
||||
type: REQUEST_HEADER_CHANGED,
|
||||
data: {
|
||||
index: index,
|
||||
keyName: key,
|
||||
newValue: newValue,
|
||||
isDisabled: isDisabled,
|
||||
},
|
||||
});
|
||||
|
||||
const addRequestHeader = (key, value) => ({
|
||||
type: REQUEST_HEADER_ADDED,
|
||||
data: {
|
||||
key: key,
|
||||
value: value,
|
||||
},
|
||||
});
|
||||
|
||||
const removeRequestHeader = index => {
|
||||
return {
|
||||
type: REQUEST_HEADER_REMOVED,
|
||||
data: index,
|
||||
};
|
||||
};
|
||||
|
||||
// This method adds the new header and moves the empty header to the bottom of the list
|
||||
const getHeadersAfterAddingNewHeader = (headers, newHeader) => {
|
||||
const nonEmptyHeaders = headers.filter(header => {
|
||||
return !header.isNewHeader;
|
||||
});
|
||||
nonEmptyHeaders.push(newHeader);
|
||||
nonEmptyHeaders.push({
|
||||
key: '',
|
||||
value: '',
|
||||
isActive: false,
|
||||
isNewHeader: true,
|
||||
});
|
||||
return nonEmptyHeaders;
|
||||
};
|
||||
|
||||
// This method adds a new empty header if no empty header is present
|
||||
const getChangedHeaders = (headers, changedHeaderDetails) => {
|
||||
const newHeaders = Object.assign([], headers);
|
||||
if (newHeaders[changedHeaderDetails.index].isNewHeader) {
|
||||
newHeaders[changedHeaderDetails.index].isNewHeader = false;
|
||||
newHeaders[changedHeaderDetails.index].isActive = true;
|
||||
newHeaders[changedHeaderDetails.index].isDisabled = false;
|
||||
}
|
||||
if (changedHeaderDetails.keyName === 'isActive') {
|
||||
newHeaders[changedHeaderDetails.index].isActive = !newHeaders[
|
||||
changedHeaderDetails.index
|
||||
].isActive;
|
||||
} else {
|
||||
newHeaders[changedHeaderDetails.index][changedHeaderDetails.keyName] =
|
||||
changedHeaderDetails.newValue;
|
||||
}
|
||||
if (changedHeaderDetails.isDisabled === true) {
|
||||
newHeaders[changedHeaderDetails.index].isDisabled = true;
|
||||
} else {
|
||||
newHeaders[changedHeaderDetails.index].isDisabled = false;
|
||||
}
|
||||
const nonEmptyHeaders = newHeaders.filter(header => {
|
||||
return !header.isNewHeader;
|
||||
});
|
||||
nonEmptyHeaders.push({
|
||||
key: '',
|
||||
value: '',
|
||||
isActive: false,
|
||||
isNewHeader: true,
|
||||
isDisabled: false,
|
||||
});
|
||||
return nonEmptyHeaders;
|
||||
};
|
||||
|
||||
const apiExplorerReducer = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case REQUEST_HEADER_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
displayedApi: {
|
||||
...state.displayedApi,
|
||||
request: {
|
||||
...state.displayedApi.request,
|
||||
headers: getChangedHeaders(
|
||||
state.displayedApi.request.headers,
|
||||
action.data
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
case REQUEST_HEADER_ADDED:
|
||||
return {
|
||||
...state,
|
||||
displayedApi: {
|
||||
...state.displayedApi,
|
||||
request: {
|
||||
...state.displayedApi.request,
|
||||
headers: getHeadersAfterAddingNewHeader(
|
||||
state.displayedApi.request.headers,
|
||||
{
|
||||
key: action.data.key,
|
||||
value: action.data.value,
|
||||
isActive: true,
|
||||
isNewHeader: false,
|
||||
}
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
case REQUEST_HEADER_REMOVED:
|
||||
return {
|
||||
...state,
|
||||
displayedApi: {
|
||||
...state.displayedApi,
|
||||
request: {
|
||||
...state.displayedApi.request,
|
||||
headers: state.displayedApi.request.headers.filter((header, i) => {
|
||||
return !(i === action.data);
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
case UNFOCUS_ROLE_HEADER:
|
||||
return {
|
||||
...state,
|
||||
headerFocus: false,
|
||||
};
|
||||
case FOCUS_ROLE_HEADER:
|
||||
return {
|
||||
...state,
|
||||
headerFocus: true,
|
||||
};
|
||||
case GRAPHQL_ENDPOINT_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
graphqlEndpoint: action.data,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default apiExplorerReducer;
|
||||
|
||||
export {
|
||||
changeRequestHeader,
|
||||
addRequestHeader,
|
||||
removeRequestHeader,
|
||||
graphQLFetcherFinal,
|
||||
focusHeaderTextbox,
|
||||
unfocusTypingHeader,
|
||||
getRemoteQueries,
|
||||
updateGraphQLEndpoint,
|
||||
};
|
@ -0,0 +1,75 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ApiRequestWrapper from './ApiRequestWrapper';
|
||||
import Helmet from 'react-helmet';
|
||||
import { push } from 'react-router-redux';
|
||||
|
||||
class ApiExplorer extends Component {
|
||||
componentWillMount() {
|
||||
let localStorageUrl;
|
||||
|
||||
if (window.__env.graphqlEndpoint && window.__env.graphqlEndpoint !== 'undefined') {
|
||||
localStorageUrl = window.__env.graphqlEndpoint;
|
||||
} else {
|
||||
localStorageUrl = window.localStorage.getItem('ONLINE_GRAPHIQL_ENDPOINT');
|
||||
}
|
||||
|
||||
if (!this.props.graphqlEndpoint && (localStorageUrl === 'undefined' || localStorageUrl === null)) {
|
||||
this.props.dispatch(push('/'));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let localStorageUrl;
|
||||
|
||||
if (window.__env.graphqlEndpoint && window.__env.graphqlEndpoint !== 'undefined') {
|
||||
localStorageUrl = window.__env.graphqlEndpoint;
|
||||
} else {
|
||||
localStorageUrl = window.localStorage.getItem('ONLINE_GRAPHIQL_ENDPOINT');
|
||||
}
|
||||
|
||||
const styles = require('./ApiExplorer.scss');
|
||||
|
||||
const wrapperClass = styles.apiExplorerWrapper;
|
||||
const requestStyles = '';
|
||||
const wdClass = '';
|
||||
|
||||
const requestWrapper = (
|
||||
<ApiRequestWrapper
|
||||
credentials={this.props.credentials}
|
||||
explorerData={this.props.explorerData}
|
||||
details={this.props.displayedApi.details}
|
||||
request={this.props.displayedApi.request}
|
||||
requestStyles={requestStyles}
|
||||
dispatch={this.props.dispatch}
|
||||
wdStyles={wdClass}
|
||||
route={this.props.route}
|
||||
dataHeaders={this.props.dataHeaders}
|
||||
headerFocus={this.props.headerFocus}
|
||||
queryParams={this.props.location.query}
|
||||
graphqlEndpoint={localStorageUrl}
|
||||
auth={this.props.route.auth}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={'container-fluid ' + styles.padd_remove}>
|
||||
<Helmet
|
||||
title="GraphiQL Online with Headers | Built by Hasura"
|
||||
description="An online version of GraphiQL. Manage headers easily. Test your GraphQL servers."
|
||||
/>
|
||||
<div className={wrapperClass}>{requestWrapper}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ApiExplorer.propTypes = {
|
||||
modalState: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
route: PropTypes.object.isRequired,
|
||||
headerFocus: PropTypes.bool.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default ApiExplorer;
|
@ -0,0 +1,867 @@
|
||||
@import "../Common/Common.scss";
|
||||
.display_inl {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.width_80 {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.responseHeader {
|
||||
color: #788095;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.admin_token_align {
|
||||
vertical-align: middle;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.marginBottom {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.apiExplorerMini {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.wrapperOnBoarding {
|
||||
// width: 80% !important;
|
||||
}
|
||||
|
||||
.panelGreyed {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.requestGreyed {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.panelInFocus {
|
||||
|
||||
}
|
||||
|
||||
.requestInFocus {
|
||||
|
||||
}
|
||||
|
||||
.cursorNotAllowed {
|
||||
cursor: not-allowed;
|
||||
|
||||
button {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.authBtnWrapper button {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ddd;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.overlay .overlayContent {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
transform: translate(-50%,-50%);
|
||||
-ms-transform: translate(-50%,-50%);
|
||||
}
|
||||
|
||||
.overlay .overlayHeading {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.overlay .overlayMessage {
|
||||
color: #5f5f5f;
|
||||
}
|
||||
|
||||
.overlay .overlayAction {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.apiExplorerWrapper {
|
||||
display: flex;
|
||||
height: $mainContainerHeight;
|
||||
.ApiRequestWrapperVH {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.apiCollectionWrapper {
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
// Changed it from overfllow-y: scroll
|
||||
|
||||
.apiCollectionTabWrapper {
|
||||
.apiCollectionTab {
|
||||
-webkit-padding-start: 0px;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
-moz-padding-start: 0px;
|
||||
-moz-margin-before: 0;
|
||||
-moz-margin-after: 0;
|
||||
border-bottom: 1px solid #D8D8D8;
|
||||
position: absolute;
|
||||
width: 20%;
|
||||
min-width: 240px;
|
||||
background-color: #fff;
|
||||
z-index: 10;
|
||||
|
||||
.apiCollectionTabList {
|
||||
list-style-type: none;
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
padding: 10px 11px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #D8D8D8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.activeApiCollectionTab {
|
||||
border-bottom: 3px solid #ffca27;
|
||||
color: #6B6B6B;
|
||||
}
|
||||
|
||||
.apiCollectionTabList:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.apiCollectionClearHistory {
|
||||
padding: 0 15px;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
/*
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transform: translateX(-88%);
|
||||
*/
|
||||
|
||||
.apiCollectionClearHistoryButton {
|
||||
/*
|
||||
float: right;
|
||||
margin: 10px 0;
|
||||
cursor: pointer;
|
||||
*/
|
||||
margin: 10px 0;
|
||||
cursor: pointer;
|
||||
position: fixed;
|
||||
bottom: 15px;
|
||||
text-align: center;
|
||||
width: 20%;
|
||||
margin-left: -15px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #ccc;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
i {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.apiPaddTop {
|
||||
padding-top: 46px !important;
|
||||
}
|
||||
|
||||
.apiCollectionTabListDetails {
|
||||
padding: 0 15px;
|
||||
margin: 10px 0;
|
||||
// padding-top: 46px;
|
||||
|
||||
.apiCollectionTabListHead {
|
||||
padding-left: 15px;
|
||||
padding-bottom: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
|
||||
.serviceBaseDomain {
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
.add_ellipsis {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.apiCollectionGetPost {
|
||||
|
||||
.apiCollectionGetWrapper {
|
||||
padding: 5px 0;
|
||||
cursor: pointer;
|
||||
|
||||
.apiCollectionGet {
|
||||
text-align: left;
|
||||
color: #70CD00;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.apiCollectionGetDetailsWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.apiCollectionGetDetails {
|
||||
word-wrap: break-word;
|
||||
padding-right: 10px !important
|
||||
}
|
||||
|
||||
.apiCollectionPostDetails {
|
||||
word-wrap: break-word;
|
||||
padding-right: 10px !important
|
||||
}
|
||||
|
||||
.apiRightArrowWrapper {
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.activeApiCollectionGetWrapperIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.activeApiCollectionGetWrapper {
|
||||
background-color: #FFF3D5;
|
||||
border-radius: 4px;
|
||||
|
||||
.activeApiCollectionGetWrapperIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.apiCollectionPostWrapper {
|
||||
padding: 5px 0;
|
||||
cursor: pointer;
|
||||
|
||||
.apiCollectionPost {
|
||||
text-align: left;
|
||||
color: #FD9540;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.apiCollectionGetDetailsWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.apiCollectionGetDetails {
|
||||
word-wrap: break-word;
|
||||
padding-right: 10px !important
|
||||
}
|
||||
|
||||
.apiCollectionPostDetails {
|
||||
word-wrap: break-word;
|
||||
padding-right: 10px !important
|
||||
}
|
||||
|
||||
.apiRightArrowWrapper {
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.activeApiCollectionGetWrapperIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.activeApiCollectionGetWrapper {
|
||||
background-color: #FFF3D5;
|
||||
border-radius: 4px;
|
||||
|
||||
.activeApiCollectionGetWrapperIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apiContentPadd {
|
||||
padding-top: 20px;
|
||||
border-color: rgba(23, 42, 58, .1);
|
||||
border-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
padding-bottom: 10px;
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.closeHeader {
|
||||
cursor: pointer;
|
||||
padding-top: 8px;
|
||||
font-size: 16px;
|
||||
float: right;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.showAccessKey{
|
||||
cursor: pointer;
|
||||
padding-top: 8px;
|
||||
padding-right: 8px;
|
||||
font-size: 16px;
|
||||
float: left;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.apiRequestWrapper {
|
||||
|
||||
.file_upload_wrapper {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #fff;
|
||||
margin-bottom: 15px;
|
||||
input[type="file"] {
|
||||
display: inline-block;
|
||||
width: 162px;
|
||||
}
|
||||
}
|
||||
|
||||
.apiRequestheader {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
padding-bottom: 10px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.apiHasura {
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
i {
|
||||
color: #757575;;
|
||||
font-size: 22px;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.built {
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 16px;
|
||||
|
||||
i {
|
||||
color: #f93c18;
|
||||
}
|
||||
}
|
||||
|
||||
.changeEndpoint {
|
||||
padding: 10px 10px !important;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.apiRequestContent {
|
||||
font-size: 14px;
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
|
||||
a {
|
||||
color: #FEC53D;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #FEC53D;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
border: 1px solid #767E93;
|
||||
padding: 1px 4px !important;
|
||||
color: #767E93;
|
||||
}
|
||||
}
|
||||
|
||||
.apiPostRequestWrapper {
|
||||
// padding: 20px 0;
|
||||
// padding-top: 20px;
|
||||
padding-top: 20px;
|
||||
background-color: #f8fafb;
|
||||
|
||||
.inputGroupWrapper {
|
||||
-webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
|
||||
.inputGroupBtn {
|
||||
button {
|
||||
width: 100px;
|
||||
border: 0;
|
||||
padding: 10px 12px;
|
||||
background-color: #F9F9F9;
|
||||
color: #FD9540;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
|
||||
.caret {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inputGroupInput {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
padding: 10px 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.sendBtn {
|
||||
button {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
height: 39px;
|
||||
color: #606060;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
background-color: #FEC53D;
|
||||
border: 1px solid #FEC53D;
|
||||
/*
|
||||
background-color: #FFCA27;
|
||||
border: 1px solid #FFCA27;
|
||||
*/
|
||||
|
||||
&:hover {
|
||||
background-color: #F2B130;
|
||||
}
|
||||
}
|
||||
/*
|
||||
button:hover
|
||||
{
|
||||
border: 1px solid #F2B130;
|
||||
background-color: #F2B130;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
.generateBtn {
|
||||
button {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
height: 39px;
|
||||
border: 1px solid #606060;
|
||||
color: #606060;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border: 1px solid #606060;
|
||||
background-color: #efefef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.responseWrapper {
|
||||
clear: both;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.responseHeader {
|
||||
color: #788095;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
|
||||
.viewDetails {
|
||||
padding-left: 10px;
|
||||
font-weight: normal;
|
||||
color: #FFCA27;
|
||||
}
|
||||
|
||||
.addAdminToken {
|
||||
text-align: right;
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.addAdminToken {
|
||||
text-align: right;
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apiResponseWrapper {
|
||||
.apiResponseheaderWrapper {
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.apiResponseheader {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
padding-bottom: 10px;
|
||||
// color: #000;
|
||||
color: #788095
|
||||
}
|
||||
|
||||
.statusDetails {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
padding-left: 20px;
|
||||
|
||||
.statusView {
|
||||
padding-left: 5px;
|
||||
font-weight: normal;
|
||||
color: #FFCA27;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.helpTextWrapper {
|
||||
padding: 15px;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #fff;
|
||||
clear: both;
|
||||
margin-bottom: 20px;
|
||||
|
||||
i {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 10px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.copyBtn {
|
||||
padding: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestionTextColor {
|
||||
color: #111;
|
||||
background-color: #ffd760;
|
||||
border-color: #ffd760;
|
||||
}
|
||||
|
||||
.noResponseWrapper {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
background-color: #fff;
|
||||
-webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
clear: both;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.noResponseContainer {
|
||||
width: 325px;
|
||||
|
||||
.noResponseHeader {
|
||||
font-size: 18px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.barWrapper {
|
||||
padding-top: 15px;
|
||||
text-align: center;
|
||||
|
||||
.bigBar {
|
||||
width: 56%;
|
||||
margin-right: 7px;
|
||||
height: 20px;
|
||||
background-color: #EEEEEE;
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mediumBar {
|
||||
width: 23%;
|
||||
margin-right: 7px;
|
||||
height: 20px;
|
||||
background-color: #FFCA27;
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.smallBar {
|
||||
width: 13%;
|
||||
margin-right: 7px;
|
||||
height: 20px;
|
||||
background-color: #EEEEEE;
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.responseHeader {
|
||||
padding-top: 15px;
|
||||
color: #788095;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
clear: both;
|
||||
|
||||
.viewDetails {
|
||||
padding-left: 10px;
|
||||
font-weight: normal;
|
||||
color: #FFCA27;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common
|
||||
.responseTable {
|
||||
padding-top: 15px;
|
||||
|
||||
.tableBorder {
|
||||
background-color: #fff;
|
||||
border: 1px solid #E3E5E5;
|
||||
thead {
|
||||
tr {
|
||||
th {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
td {
|
||||
border-top: 0;
|
||||
// padding: 5px;
|
||||
padding: 0px 5px;
|
||||
min-width: 50px;
|
||||
|
||||
.responseTableInput {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
// padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.headerPadd {
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.borderTop {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.tableTdLeft {
|
||||
padding-left: 5%;
|
||||
}
|
||||
|
||||
.tableEnterKey {
|
||||
// padding: 10px 0;
|
||||
padding: 0px 5px 0px 4.5%;
|
||||
}
|
||||
|
||||
.tableLastTd {
|
||||
padding-left: 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headerHeading {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.queryBuilderWrapper {
|
||||
padding-bottom: 20px;
|
||||
|
||||
.queryBuilderTab {
|
||||
ul {
|
||||
border: 1px solid #E7E7E7;
|
||||
-webkit-padding-start: 0px;
|
||||
-moz-padding-start: 0px;
|
||||
display: inline-block;
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
display: inline-block;
|
||||
padding: 12px 20px;
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
color: #788094;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
li:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.activeQueryBuilderTab {
|
||||
background-color: #FFF050;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.AceEditorWrapper {
|
||||
-webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
/*
|
||||
margin-top: 15px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
*/
|
||||
}
|
||||
|
||||
.queryBuilderLayout {
|
||||
background-color: #fff;
|
||||
// padding: 15px;
|
||||
}
|
||||
|
||||
.queryBuilderLayoutSub {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.qbTabHeading {
|
||||
font-weight: bold;
|
||||
padding-top: 15px;
|
||||
padding-left: 20px;
|
||||
padding-bottom: 15px;
|
||||
i {
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.common_checkbox {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
|
||||
.common_checkbox_label {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 0px;
|
||||
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.common_checkbox_label {
|
||||
margin-bottom: 0px !important;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.common_checkbox + .common_checkbox_label:before {
|
||||
content: '';
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding-top: 2px;
|
||||
margin-right: 2px;
|
||||
text-align: center;
|
||||
border-radius:4px;
|
||||
cursor:pointer;
|
||||
}
|
||||
label
|
||||
{
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.common_checkbox:checked + .common_checkbox_label:before {
|
||||
content: url('./tick.png');
|
||||
background: #FFCA27;
|
||||
color: #fff;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.authPanelSubHeadings {
|
||||
font-size: 16px;
|
||||
font-weight: italic;
|
||||
padding-left: 15px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.apiResponseTab {
|
||||
padding-top: 20px;
|
||||
|
||||
.apiResponseTabUl {
|
||||
display: inline-block;
|
||||
-webkit-padding-start: 0px;
|
||||
-webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.apiResponseTabList {
|
||||
display: inline-block;
|
||||
padding: 15px 25px;
|
||||
list-style-type: none;
|
||||
background-color: #fff;
|
||||
font-weight: 600;
|
||||
color: #6B6B6B;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.apiResponseTabList:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.activeApiResponseTab {
|
||||
background-color: #FFF3D5
|
||||
}
|
||||
}
|
||||
|
||||
.apiResponseTabPanel {
|
||||
.AceEditorWrapper {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import ApiExplorer from './ApiExplorer';
|
||||
|
||||
const generatedApiExplorer = connect => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
...state.apiexplorer,
|
||||
credentials: {},
|
||||
};
|
||||
};
|
||||
return connect(mapStateToProps)(ApiExplorer);
|
||||
};
|
||||
|
||||
export default generatedApiExplorer;
|
@ -0,0 +1,322 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { push } from 'react-router-redux';
|
||||
|
||||
import {
|
||||
changeRequestParams,
|
||||
addRequestHeader,
|
||||
changeRequestHeader,
|
||||
removeRequestHeader,
|
||||
updateFileObject,
|
||||
focusHeaderTextbox,
|
||||
unfocusTypingHeader,
|
||||
} from './Actions';
|
||||
|
||||
import GraphiQLWrapper from './GraphiQLWrapper';
|
||||
|
||||
const styles = require('./ApiExplorer.scss');
|
||||
|
||||
class ApiRequest extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.state.accessKeyVisible = false;
|
||||
this.state.bodyAllowedMethods = ['POST'];
|
||||
this.state.tabIndex = 0;
|
||||
}
|
||||
|
||||
onRequestParamsChanged = newValue => {
|
||||
this.props.dispatch(changeRequestParams(newValue));
|
||||
};
|
||||
|
||||
onHeaderValueChanged(e) {
|
||||
const index = parseInt(e.target.getAttribute('data-header-id'), 10);
|
||||
const key = e.target.getAttribute('data-element-name');
|
||||
const newValue = e.target.value;
|
||||
this.props.dispatch(changeRequestHeader(index, key, newValue, false));
|
||||
}
|
||||
|
||||
onDeleteHeaderClicked(e) {
|
||||
const index = parseInt(e.target.getAttribute('data-header-id'), 10);
|
||||
this.props.dispatch(removeRequestHeader(index));
|
||||
}
|
||||
|
||||
onNewHeaderKeyChanged(e) {
|
||||
this.handleTypingTimeouts();
|
||||
this.props.dispatch(addRequestHeader(e.target.value, ''));
|
||||
}
|
||||
|
||||
onNewHeaderValueChanged(e) {
|
||||
this.handleTypingTimeouts();
|
||||
this.props.dispatch(addRequestHeader('', e.target.value));
|
||||
}
|
||||
|
||||
onKeyUpAtNewHeaderField(e) {
|
||||
if (e.keyCode === 13) {
|
||||
this.props.dispatch(
|
||||
addRequestHeader(this.state.newHeader.key, this.state.newHeader.value)
|
||||
);
|
||||
}
|
||||
}
|
||||
getUrlBar() {
|
||||
return (
|
||||
<div
|
||||
id="stickyHeader"
|
||||
className={
|
||||
styles.apiPostRequestWrapper +
|
||||
' ' +
|
||||
styles.wd100 +
|
||||
' ' +
|
||||
styles.stickyHeader
|
||||
}
|
||||
>
|
||||
<div className={'col-xs-12 ' + styles.padd_remove}>
|
||||
<div
|
||||
className={
|
||||
'input-group ' +
|
||||
styles.inputGroupWrapper
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
'input-group-btn ' +
|
||||
styles.inputGroupBtn
|
||||
}
|
||||
>
|
||||
<button type="button" className={'btn btn-default'}>
|
||||
POST
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
defaultValue={this.props.url}
|
||||
readOnly
|
||||
type="text"
|
||||
className={
|
||||
styles.inputGroupInput +
|
||||
' form-control '
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.stickySeparator} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getHeaderTitleView() {
|
||||
return (
|
||||
<div className={styles.responseWrapper}>
|
||||
<div className={'col-xs-12 ' + styles.padd_remove}>
|
||||
<div className={styles.responseHeader}>Request Headers</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getHeaderRows() {
|
||||
const rows = this.props.headers.map((header, i) => {
|
||||
return (
|
||||
<tr key={i}>
|
||||
{header.isNewHeader ? null : (
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="sponsored"
|
||||
className={styles.common_checkbox + ' common_checkbox'}
|
||||
id={i + 1}
|
||||
checked={header.isActive}
|
||||
data-header-id={i}
|
||||
onChange={this.onHeaderValueChanged.bind(this)}
|
||||
data-element-name="isActive"
|
||||
/>
|
||||
<label
|
||||
htmlFor={i + 1}
|
||||
className={
|
||||
styles.common_checkbox_label + ' common_checkbox_label'
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
<td
|
||||
colSpan={header.isNewHeader ? '2' : '1'}
|
||||
className={
|
||||
header.isNewHeader
|
||||
? styles.border_right +
|
||||
' ' +
|
||||
styles.tableTdLeft +
|
||||
' ' +
|
||||
styles.borderTop +
|
||||
' ' +
|
||||
styles.tableEnterKey
|
||||
: styles.border_right
|
||||
}
|
||||
>
|
||||
<input
|
||||
className={'form-control ' + styles.responseTableInput}
|
||||
value={header.key}
|
||||
disabled={header.isDisabled === true ? true : false}
|
||||
data-header-id={i}
|
||||
placeholder="Enter Key"
|
||||
data-element-name="key"
|
||||
onChange={this.onHeaderValueChanged.bind(this)}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
type="text"
|
||||
data-test={`header-key-${i}`}
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
colSpan={header.isNewHeader ? '2' : '1'}
|
||||
className={
|
||||
header.isNewHeader
|
||||
? styles.borderTop +
|
||||
' ' +
|
||||
styles.tableEnterKey +
|
||||
' ' +
|
||||
styles.tableLastTd
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<input
|
||||
className={'form-control ' + styles.responseTableInput}
|
||||
value={header.value}
|
||||
disabled={header.isDisabled === true ? true : false}
|
||||
data-header-id={i}
|
||||
placeholder="Enter Value"
|
||||
data-element-name="value"
|
||||
onChange={this.onHeaderValueChanged.bind(this)}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
data-test={`header-value-${i}`}
|
||||
type={
|
||||
header.key === 'X-Hasura-Admin-Secret' &&
|
||||
!this.state.accessKeyVisible
|
||||
? 'password'
|
||||
: 'text'
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
{header.isNewHeader ? null : (
|
||||
<td>
|
||||
{header.key === 'X-Hasura-Admin-Secret' ? (
|
||||
<i
|
||||
className={styles.showAccessKey + ' fa fa-eye'}
|
||||
data-header-id={i}
|
||||
aria-hidden="true"
|
||||
onClick={this.onShowAccessKeyClicked.bind(this)}
|
||||
/>
|
||||
) : null}
|
||||
<i
|
||||
className={styles.closeHeader + ' fa fa-times'}
|
||||
data-header-id={i}
|
||||
aria-hidden="true"
|
||||
onClick={this.onDeleteHeaderClicked.bind(this)}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
return rows;
|
||||
}
|
||||
|
||||
getHeaderTableView() {
|
||||
return (
|
||||
<div className={styles.responseTable}>
|
||||
<table className={'table ' + styles.tableBorder}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={styles.wd4 + ' ' + styles.headerHeading} />
|
||||
<th
|
||||
className={
|
||||
styles.wd48 +
|
||||
' ' +
|
||||
styles.border_right +
|
||||
' ' +
|
||||
styles.headerHeading
|
||||
}
|
||||
>
|
||||
Key
|
||||
</th>
|
||||
<th className={styles.wd48 + ' ' + styles.headerHeading}>
|
||||
Value
|
||||
</th>
|
||||
<th className={styles.wd4 + ' ' + styles.headerHeading} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{this.getHeaderRows()}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getHeaderBody() {
|
||||
return (
|
||||
<div className={styles.responseHeader + ' ' + styles.marginBottom}>
|
||||
Request Body
|
||||
</div>
|
||||
);
|
||||
}
|
||||
getValidBody() {
|
||||
switch (this.props.bodyType) {
|
||||
case 'graphql':
|
||||
return (
|
||||
<GraphiQLWrapper
|
||||
data={this.props}
|
||||
dispatch={this.props.dispatch}
|
||||
headerFocus={this.props.headerFocus}
|
||||
queryParams={this.props.queryParams}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
changeEndpoint() {
|
||||
this.props.dispatch(push('/'));
|
||||
}
|
||||
|
||||
handleFocus = () => {
|
||||
this.props.dispatch(focusHeaderTextbox());
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
this.props.dispatch(unfocusTypingHeader());
|
||||
};
|
||||
|
||||
handleFileChange(e) {
|
||||
if (e.target.files.length > 0) {
|
||||
this.props.dispatch(updateFileObject(e.target.files[0]));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.apiRequestWrapper}>
|
||||
{this.getUrlBar()}
|
||||
<hr />
|
||||
{this.getHeaderTitleView()}
|
||||
{this.getHeaderTableView()}
|
||||
{this.getValidBody()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ApiRequest.propTypes = {
|
||||
method: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
headers: PropTypes.array,
|
||||
params: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
explorerData: PropTypes.object.isRequired,
|
||||
credentials: PropTypes.object.isRequired,
|
||||
bodyType: PropTypes.string.isRequired,
|
||||
route: PropTypes.object.isRequired,
|
||||
numberOfTables: PropTypes.number.isRequired,
|
||||
headerFocus: PropTypes.bool.isRequired,
|
||||
queryParams: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default ApiRequest;
|
@ -0,0 +1,76 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ApiRequestDetails extends Component {
|
||||
login() {
|
||||
this.props.auth.login();
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.props.auth.logout();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isAuthenticated } = this.props.auth;
|
||||
|
||||
const styles = require('./ApiExplorer.scss');
|
||||
|
||||
const loginButton = (
|
||||
<button
|
||||
id="qsLoginBtn"
|
||||
className={'btn btn-primary'}
|
||||
onClick={this.login.bind(this)}
|
||||
>
|
||||
Log In
|
||||
</button>
|
||||
);
|
||||
|
||||
const logoutButton = (
|
||||
<button
|
||||
id="qsLogoutBtn"
|
||||
className={'btn btn-danger'}
|
||||
onClick={this.logout.bind(this)}
|
||||
>
|
||||
Log Out
|
||||
</button>
|
||||
);
|
||||
|
||||
const loginOverlay = (
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.overlayContent}>
|
||||
<div className={styles.overlayHeading}>
|
||||
GraphiQL
|
||||
</div>
|
||||
<div className={styles.overlayMessage}>
|
||||
Login to explore your GraphQL APIs securely.
|
||||
</div>
|
||||
<div className={styles.overlayAction}>
|
||||
{loginButton}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ isAuthenticated() || loginOverlay }
|
||||
|
||||
<div className={styles.apiRequestWrapper + ' ' + styles.apiContentPadd}>
|
||||
<div className={styles.apiRequestContent}>{this.props.description}</div>
|
||||
|
||||
<div className={styles.authBtnWrapper}>
|
||||
{ isAuthenticated() ? logoutButton : loginButton }
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ApiRequestDetails.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ApiRequestDetails;
|
@ -0,0 +1,67 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ApiRequest from './ApiRequest';
|
||||
import ApiRequestDetails from './ApiRequestDetails';
|
||||
|
||||
class ApiRequestWrapper extends Component {
|
||||
render() {
|
||||
const styles = require('./ApiExplorer.scss');
|
||||
return (
|
||||
<div
|
||||
id="apiRequestBlock"
|
||||
className={
|
||||
this.props.wdStyles +
|
||||
' ' +
|
||||
styles.padd_left +
|
||||
' ' +
|
||||
styles.padd_right +
|
||||
' ' +
|
||||
styles.ApiRequestWrapperVH +
|
||||
' ' +
|
||||
this.props.requestStyles
|
||||
}
|
||||
>
|
||||
<ApiRequestDetails
|
||||
title={this.props.details.title}
|
||||
description={this.props.details.description}
|
||||
auth={this.props.auth}
|
||||
/>
|
||||
<ApiRequest
|
||||
bodyType={
|
||||
this.props.request.bodyType ? this.props.request.bodyType : ''
|
||||
}
|
||||
credentials={this.props.credentials}
|
||||
method={this.props.request.method}
|
||||
url={this.props.graphqlEndpoint}
|
||||
headers={this.props.request.headers}
|
||||
params={this.props.request.params}
|
||||
explorerData={this.props.explorerData}
|
||||
dispatch={this.props.dispatch}
|
||||
dataHeaders={this.props.dataHeaders}
|
||||
numberOfTables={this.props.numberOfTables}
|
||||
headerFocus={this.props.headerFocus}
|
||||
queryParams={this.props.queryParams}
|
||||
auth={this.props.auth}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ApiRequestWrapper.propTypes = {
|
||||
details: PropTypes.object.isRequired,
|
||||
request: PropTypes.object.isRequired,
|
||||
explorerData: PropTypes.object.isRequired,
|
||||
credentials: PropTypes.object.isRequired,
|
||||
bodyType: PropTypes.string,
|
||||
showHelpBulb: PropTypes.bool,
|
||||
requestStyles: PropTypes.string,
|
||||
wdStyles: PropTypes.string,
|
||||
dispatch: PropTypes.func,
|
||||
numberOfTables: PropTypes.number,
|
||||
headerFocus: PropTypes.bool.isRequired,
|
||||
graphqlEndpoint: PropTypes.string,
|
||||
queryParams: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default ApiRequestWrapper;
|
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="-216 218 66 66" style="enable-background:new -216 218 66 66;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#97A9FF;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_1">
|
||||
<title>Discord</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Page-1">
|
||||
<g id="Hasura_Jun29-Update" transform="translate(-1157.000000, -872.000000)">
|
||||
<g id="Group-6" transform="translate(1066.000000, 872.000000)">
|
||||
<g id="Discord" transform="translate(91.000000, 0.000000)">
|
||||
<g>
|
||||
<g id="Twitter-Copy">
|
||||
<path id="back" class="st0" d="M-216,251c0-18.2,14.8-33,33-33s33,14.8,33,33s-14.8,33-33,33S-216,269.2-216,251z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path class="st1" d="M-172.5,266.6l-2.5-2.9c4.8-1.3,6.7-4.6,6.7-4.6c-6.1,5.7-22.6,6-29.1,0c0,0,1.6,3,6.5,4.6l-2.7,2.9
|
||||
c-8.2-0.1-11.3-5.6-11.3-5.6c0-12.1,5.4-21.8,5.4-21.8c5.5-3.9,10.5-3.9,10.5-3.9l0.4,0.5c-6.8,1.9-9.7,4.9-9.7,4.9
|
||||
c7.9-5.4,24.3-4.9,30.7,0c0.1-0.1-3.9-3.3-10-4.9l0.6-0.5c0,0,5,0,10.5,3.9c0,0,5.4,9.7,5.4,21.8
|
||||
C-161.2,261-164.3,266.5-172.5,266.6z"/>
|
||||
<ellipse class="st0" cx="-190" cy="253.5" rx="3.9" ry="4.1"/>
|
||||
<ellipse class="st0" cx="-176.5" cy="253.5" rx="3.9" ry="4.1"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="8" viewBox="0 0 13 8"><path fill="none" stroke="#4D4D4D" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" opacity=".7" transform="rotate(90 5 6.5)" d="M0 10L5 5 0 0"/></svg>
|
After Width: | Height: | Size: 249 B |
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, info: null };
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
this.setState({ hasError: true, info: info });
|
||||
// most likely a localstorage issue
|
||||
window.localStorage.clear();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// You can render any custom fallback UI
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBoundary.propTypes = {
|
||||
children: PropTypes.element,
|
||||
};
|
||||
|
||||
export default ErrorBoundary;
|
@ -0,0 +1,149 @@
|
||||
import React, { Component } from 'react';
|
||||
import GraphiQL from 'hasura-console-graphiql';
|
||||
import { getHeadersAsJSON } from './utils';
|
||||
import { getIntrospectionQuery, buildClientSchema } from 'graphql';
|
||||
import PropTypes from 'prop-types';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import { graphQLFetcherFinal, getRemoteQueries } from './Actions';
|
||||
import GraphiQLExplorer from 'graphiql-explorer';
|
||||
import { makeDefaultArg, getDefaultScalarArgValue } from './onegraphUtils';
|
||||
|
||||
import './GraphiQL.css';
|
||||
|
||||
class GraphiQLWrapper extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
schema: null,
|
||||
error: false,
|
||||
queries: null,
|
||||
onBoardingEnabled: false,
|
||||
explorerIsOpen: true,
|
||||
query: ''
|
||||
};
|
||||
const queryFile = this.props.queryParams
|
||||
? this.props.queryParams.query_file
|
||||
: null;
|
||||
if (queryFile) {
|
||||
getRemoteQueries(queryFile, queries =>
|
||||
this.setState({ ...this.state, queries })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_handleEditQuery = (query: string): void => this.setState({query});
|
||||
|
||||
_handleToggleExplorer = () => {
|
||||
this.setState({explorerIsOpen: !this.state.explorerIsOpen});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !nextProps.headerFocus;
|
||||
}
|
||||
|
||||
introspect() {
|
||||
const { url, headers } = this.props.data;
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: getHeadersAsJSON(headers || []),
|
||||
body: JSON.stringify({
|
||||
query: getIntrospectionQuery(),
|
||||
}),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
this.setState({
|
||||
schema: buildClientSchema(result.data)
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({
|
||||
schema: null
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.introspect();
|
||||
}
|
||||
|
||||
render() {
|
||||
const styles = require('../Common/Common.scss');
|
||||
const { variables, query } = window.__env;
|
||||
const graphQLFetcher = graphQLParams => {
|
||||
return graphQLFetcherFinal(
|
||||
graphQLParams,
|
||||
this.props.data.url,
|
||||
this.props.data.headers
|
||||
);
|
||||
};
|
||||
const graphiqlProps = {
|
||||
fetcher: graphQLFetcher,
|
||||
};
|
||||
if (query || Object.keys(variables).length !== 0) {
|
||||
graphiqlProps.query = query;
|
||||
if (variables !== 'undefined') {
|
||||
graphiqlProps.variables = JSON.stringify(variables, null, 2);
|
||||
}
|
||||
} else if (this.state.queries) {
|
||||
graphiqlProps.query = this.state.queries;
|
||||
}
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div
|
||||
className={
|
||||
'react-container-graphql ' +
|
||||
styles.wd100 +
|
||||
' ' +
|
||||
styles.graphQLHeight
|
||||
}
|
||||
>
|
||||
<div className="graphiql-container">
|
||||
<GraphiQLExplorer
|
||||
schema={this.state.schema}
|
||||
query={this.state.query}
|
||||
onEdit={this._handleEditQuery}
|
||||
explorerIsOpen={this.state.explorerIsOpen}
|
||||
onToggleExplorer={this._handleToggleExplorer}
|
||||
getDefaultScalarArgValue={getDefaultScalarArgValue}
|
||||
makeDefaultArg={makeDefaultArg}
|
||||
/>
|
||||
<GraphiQL {...graphiqlProps}
|
||||
ref={ref => (this._graphiql = ref)}
|
||||
schema={this.state.schema}
|
||||
query={this.state.query}
|
||||
onEditQuery={this._handleEditQuery}
|
||||
>
|
||||
<GraphiQL.Toolbar>
|
||||
<GraphiQL.Button
|
||||
onClick={() => this._graphiql.handlePrettifyQuery()}
|
||||
label="Prettify"
|
||||
title="Prettify Query (Shift-Ctrl-P)"
|
||||
/>
|
||||
<GraphiQL.Button
|
||||
onClick={() => this._graphiql.handleToggleHistory()}
|
||||
label="History"
|
||||
title="Show History"
|
||||
/>
|
||||
<GraphiQL.Button
|
||||
onClick={this._handleToggleExplorer}
|
||||
label="Explorer"
|
||||
title="Toggle Explorer"
|
||||
/>
|
||||
</GraphiQL.Toolbar>
|
||||
</GraphiQL>
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GraphiQLWrapper.propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
headerFocus: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default GraphiQLWrapper;
|
@ -0,0 +1,351 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Raleway:300,400,600');
|
||||
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600');
|
||||
body
|
||||
{
|
||||
font-size: 16px;
|
||||
font-family: 'Open Sans'
|
||||
}
|
||||
.wd100
|
||||
{
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
.displayFlex
|
||||
{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.boxShadow
|
||||
{
|
||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.3);
|
||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.commonBtn
|
||||
{
|
||||
text-align: center;
|
||||
padding: 25px 0;
|
||||
clear: both;
|
||||
button
|
||||
{
|
||||
border-radius: 25px;
|
||||
background-color: #4767a1;
|
||||
padding: 15px 30px;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
font-family: 'Raleway';
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
button:hover
|
||||
{
|
||||
background-color: #3b5f93;
|
||||
}
|
||||
button:focus
|
||||
{
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
.lightGrayBgColor
|
||||
{
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
.blueBgColor
|
||||
{
|
||||
background-color: #303b4e;
|
||||
}
|
||||
.pageHeader
|
||||
{
|
||||
font-family: 'Raleway';
|
||||
font-weight: 600;
|
||||
font-size: 44px;
|
||||
color: #4D4D4D;
|
||||
}
|
||||
.pageSubHeader
|
||||
{
|
||||
font-family: 'Raleway';
|
||||
font-weight: 600;
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
color: #102261;
|
||||
}
|
||||
.pageDescription
|
||||
{
|
||||
text-align: center;
|
||||
color: #565656;
|
||||
padding-bottom: 15px;
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
font-family: 'Open Sans';
|
||||
a
|
||||
{
|
||||
color: #f93c18;
|
||||
}
|
||||
a:hover
|
||||
{
|
||||
color: #e0270e;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.pageDescriptionSmall
|
||||
{
|
||||
text-align: center;
|
||||
color: #f93c18;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding-bottom: 10px;
|
||||
font-family: 'Open Sans';
|
||||
a
|
||||
{
|
||||
color: #f93c18;
|
||||
i
|
||||
{
|
||||
color: #f93c18;
|
||||
}
|
||||
}
|
||||
a:hover
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.noPadd
|
||||
{
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.commonSectionWrapper
|
||||
{
|
||||
padding: 75px 0;
|
||||
}
|
||||
|
||||
.powerfulGraphQLWrapper
|
||||
{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.pageSubHeader
|
||||
{
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
.pageSubHeader, .pageDescription
|
||||
{
|
||||
text-align: left;
|
||||
padding-right: 50px;
|
||||
}
|
||||
.powerfulGraphQLImg
|
||||
{
|
||||
text-align: right;
|
||||
img
|
||||
{
|
||||
width: 95%;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.easyStepsWrapper
|
||||
{
|
||||
.pageSubHeader
|
||||
{
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
.stepsListWrapper
|
||||
{
|
||||
.stepsList
|
||||
{
|
||||
.stepsIcon
|
||||
{
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
img
|
||||
{
|
||||
max-height: 60px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.pageDescription
|
||||
{
|
||||
padding: 0 5px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.easyStepsImg
|
||||
{
|
||||
background-color: #3edad7;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
width: 90%;
|
||||
margin: 0 0 0 auto;
|
||||
img
|
||||
{
|
||||
width: 65px;
|
||||
display: inline-block;
|
||||
}
|
||||
.pageDescription
|
||||
{
|
||||
padding-top: 20px;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
font-family: 'Raleway';
|
||||
}
|
||||
}
|
||||
}
|
||||
.footerWrapper
|
||||
{
|
||||
padding: 20px 0px;
|
||||
.socialWrapper
|
||||
{
|
||||
text-align: center;
|
||||
.socialIcon
|
||||
{
|
||||
display: inline-block;
|
||||
padding: 0px 10px;
|
||||
img
|
||||
{
|
||||
width: 50px;
|
||||
display: inline-block;
|
||||
opacity: 0.7;
|
||||
}
|
||||
img:hover
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pageDescription
|
||||
{
|
||||
color: #fff;
|
||||
i
|
||||
{
|
||||
color: #f93c18;
|
||||
}
|
||||
a
|
||||
{
|
||||
color: #fff;
|
||||
}
|
||||
a:hover
|
||||
{
|
||||
color: #f93c18;
|
||||
}
|
||||
}
|
||||
.pageDescriptionSmall
|
||||
{
|
||||
padding-top: 20px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px)
|
||||
{
|
||||
.pageHeader
|
||||
{
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.pageSubHeader
|
||||
{
|
||||
font-size: 22px !important;
|
||||
text-align: center;
|
||||
}
|
||||
.pageDescription
|
||||
{
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.pageDescriptionSmall
|
||||
{
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
.commonSectionWrapper
|
||||
{
|
||||
padding: 30px 0;
|
||||
}
|
||||
.displayFlex
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
.powerfulGraphQLWrapper
|
||||
{
|
||||
display: block;
|
||||
.pageSubHeader, .pageDescription
|
||||
{
|
||||
text-align: center;
|
||||
padding-right: 0;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
.powerfulGraphQLImg
|
||||
{
|
||||
text-align: center;
|
||||
padding-top: 20px;
|
||||
img
|
||||
{
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.easyStepsWrapper
|
||||
{
|
||||
.stepsListWrapper
|
||||
{
|
||||
.stepsList
|
||||
{
|
||||
padding-bottom: 0px;
|
||||
.stepsIcon
|
||||
{
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.easyStepsImg
|
||||
{
|
||||
width: 95%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
.footerWrapper
|
||||
{
|
||||
.pageDescriptionSmall
|
||||
{
|
||||
padding: 0px 10px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 991px)
|
||||
{
|
||||
.commonSectionWrapper
|
||||
{
|
||||
padding: 30px 0;
|
||||
}
|
||||
.pageHeader
|
||||
{
|
||||
font-size: 35px;
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.easyStepsWrapper
|
||||
{
|
||||
.stepsListWrapper
|
||||
{
|
||||
.stepsList
|
||||
{
|
||||
padding-bottom: 30px;
|
||||
.stepsIcon
|
||||
{
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.easyStepsImg
|
||||
{
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="66px" height="66px" viewBox="0 0 66 66" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Twitter</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Hasura_Jun29-Update" transform="translate(-1066.000000, -872.000000)">
|
||||
<g id="Group-6" transform="translate(1066.000000, 872.000000)">
|
||||
<g id="Twitter">
|
||||
<g>
|
||||
<path d="M0,33 C0,14.7746024 14.7746024,0 33,0 C51.2253976,0 66,14.7746024 66,33 C66,51.2253976 51.2253976,66 33,66 C14.7746024,66 0,51.2253976 0,33 Z" id="back" fill="#55ACEE"></path>
|
||||
<path d="M32.5362646,26.7393379 L32.6058642,27.8966431 L31.4458703,27.7549322 C27.2234923,27.2117073 23.5347116,25.3694664 20.4027279,22.2754464 L18.8715359,20.7402457 L18.477138,21.8739324 C17.6419423,24.401109 18.1755395,27.0699965 19.9155304,28.8650005 C20.8435256,29.8569763 20.6347267,29.9986872 19.033935,29.4082253 C18.477138,29.2192775 17.9899405,29.0775667 17.9435407,29.1484221 C17.7811416,29.3137514 18.3379387,31.4630325 18.7787364,32.3132975 C19.3819332,33.4942212 20.6115268,34.6515264 21.9571198,35.3364621 L23.0939139,35.879687 L21.7483209,35.9033055 C20.4491277,35.9033055 20.4027279,35.9269239 20.5419272,36.4229119 C21.0059248,37.9581126 22.8387152,39.5877873 24.8803045,40.2963415 L26.318697,40.7923294 L25.0659036,41.5481205 C23.2099133,42.6345703 21.0291246,43.2486506 18.848336,43.2958876 C17.8043415,43.319506 16.9459459,43.4139799 16.9459459,43.4848353 C16.9459459,43.7210201 19.7763312,45.0436546 21.4235226,45.563261 C26.3650968,47.0984617 32.2346662,46.4371445 36.6426431,43.815494 C39.7746268,41.9496346 42.9066105,38.2415343 44.3682028,34.6515264 C45.1569987,32.7384301 45.9457946,29.242896 45.9457946,27.5659844 C45.9457946,26.4795347 46.0153942,26.3378238 47.3145874,25.0388078 C48.0801834,24.2830167 48.7993797,23.4563701 48.938579,23.2201854 C49.1705778,22.7714344 49.1473779,22.7714344 47.9641841,23.1729484 C45.9921943,23.8815026 45.7137958,23.7870287 46.6881907,22.7241974 C47.407387,21.9684063 48.2657825,20.5985348 48.2657825,20.1970208 C48.2657825,20.1261654 47.9177843,20.2442577 47.5233864,20.456824 C47.1057885,20.6930087 46.1777934,21.0472858 45.481797,21.2598521 L44.2290035,21.6613661 L43.0922095,20.8819565 C42.4658128,20.456824 41.5842174,19.9844545 41.1202198,19.8427437 C39.937026,19.5120851 38.1274354,19.559322 37.060241,19.9372176 C34.1602561,21.0000489 32.3274657,23.7397918 32.5362646,26.7393379 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="7" height="14" viewBox="0 0 7 14"><path fill="#788095" d="M.535.5L6.465 7l-5.93 6.5z"/></svg>
|
After Width: | Height: | Size: 140 B |
@ -0,0 +1,21 @@
|
||||
import GraphiQLExplorer from 'graphiql-explorer';
|
||||
|
||||
export const makeDefaultArg = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getDefaultScalarArgValue = (parentField, arg, argType) => {
|
||||
return GraphiQLExplorer.defaultValue(argType);
|
||||
};
|
||||
|
||||
export const getExplorerWidthFromLocalStorage = () => {
|
||||
const val = parseInt(
|
||||
window.localStorage.getItem('graphiql:explorerWidth'),
|
||||
10
|
||||
);
|
||||
return isNaN(val) ? 350 : val;
|
||||
};
|
||||
|
||||
export const setExplorerWidthInLocalStorage = width => {
|
||||
localStorage.setItem('graphiql:explorerWidth', width);
|
||||
};
|
@ -0,0 +1,92 @@
|
||||
const existingHeaders = window.__env.headers;
|
||||
const authToken = window.localStorage.getItem('auth0:id_token');
|
||||
|
||||
const defaultHeader = [
|
||||
{
|
||||
key: 'Content-Type',
|
||||
value: 'application/json',
|
||||
isActive: true,
|
||||
isNewHeader: false,
|
||||
isDisabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
if (existingHeaders) {
|
||||
Object.keys(existingHeaders).forEach((key) => {
|
||||
defaultHeader.push({
|
||||
key,
|
||||
value: existingHeaders[key],
|
||||
isActive: true,
|
||||
isNewHeader: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
if (authToken) {
|
||||
defaultHeader.push({
|
||||
key: 'Authorization',
|
||||
value: 'Bearer ' + authToken,
|
||||
isActive: true,
|
||||
isNewHeader: false,
|
||||
isDisabled: true
|
||||
})
|
||||
}
|
||||
defaultHeader.push({
|
||||
key: '',
|
||||
value: '',
|
||||
isActive: false,
|
||||
isNewHeader: true,
|
||||
});
|
||||
|
||||
const getUrl = path => {
|
||||
return `${window.__env.graphqlEndpoint}`;
|
||||
};
|
||||
|
||||
const dataApisContent = [];
|
||||
// check project version
|
||||
dataApisContent.push({
|
||||
id: 'DataApi-3',
|
||||
details: {
|
||||
title: '',
|
||||
description:
|
||||
'Explore GraphQL APIs with headers',
|
||||
category: 'data',
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: getUrl('/v1alpha1/graphql'),
|
||||
headers: defaultHeader,
|
||||
bodyType: 'graphql',
|
||||
params: JSON.stringify({}, null, 4),
|
||||
},
|
||||
});
|
||||
|
||||
const dataApis = {
|
||||
title: 'Data',
|
||||
content: dataApisContent,
|
||||
};
|
||||
|
||||
const explorerData = {
|
||||
sendingRequest: false,
|
||||
enableResponseSection: false,
|
||||
response: {},
|
||||
fileObj: null,
|
||||
};
|
||||
|
||||
const defaultApi = dataApis.content[0];
|
||||
|
||||
const defaultState = {
|
||||
currentTab: 0,
|
||||
displayedApi: defaultApi,
|
||||
modalState: {
|
||||
isOpen: false,
|
||||
isCopied: false,
|
||||
},
|
||||
explorerData,
|
||||
authApiExpanded: 'Username-password Login',
|
||||
headerFocus: false,
|
||||
graphqlEndpoint: ''
|
||||
};
|
||||
|
||||
export default defaultState;
|
||||
|
||||
export { defaultApi, defaultHeader };
|
After Width: | Height: | Size: 156 B |
@ -0,0 +1,12 @@
|
||||
const getHeadersAsJSON = headers => {
|
||||
const headerJSON = {};
|
||||
const nonEmptyHeaders = headers.filter(header => {
|
||||
return (header.key || header.value) && header.isActive;
|
||||
});
|
||||
nonEmptyHeaders.forEach(header => {
|
||||
headerJSON[header.key] = header.value;
|
||||
});
|
||||
return headerJSON;
|
||||
};
|
||||
|
||||
export { getHeadersAsJSON };
|
@ -0,0 +1,98 @@
|
||||
import defaultState from './State';
|
||||
|
||||
const LOAD_REQUEST = 'App/ONGOING_REQUEST';
|
||||
const DONE_REQUEST = 'App/DONE_REQUEST';
|
||||
const FAILED_REQUEST = 'App/FAILED_REQUEST';
|
||||
const ERROR_REQUEST = 'App/ERROR_REQUEST';
|
||||
const CONNECTION_FAILED = 'App/CONNECTION_FAILED';
|
||||
const CLOSE_MODAL = 'App/CLOSE_MODAL';
|
||||
const NOTIF_EXPANDED = 'App/NOTIF_EXPANDED';
|
||||
const NOTIF_MSG = 'App/NOTIF_MSG';
|
||||
|
||||
const notifExpand = isExpanded => ({ type: NOTIF_EXPANDED, data: isExpanded });
|
||||
const notifMsg = finalMsg => ({ type: NOTIF_MSG, data: finalMsg });
|
||||
|
||||
const progressBarReducer = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case LOAD_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
ongoingRequest: true,
|
||||
percent: 10,
|
||||
requestSuccess: null,
|
||||
requestError: null,
|
||||
connectionFailed: false,
|
||||
};
|
||||
|
||||
case DONE_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
percent: 100,
|
||||
ongoingRequest: false,
|
||||
requestSuccess: true,
|
||||
requestError: null,
|
||||
connectionFailed: false,
|
||||
};
|
||||
|
||||
case FAILED_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
percent: 100,
|
||||
ongoingRequest: false,
|
||||
requestSuccess: null,
|
||||
requestError: true,
|
||||
connectionFailed: false,
|
||||
};
|
||||
|
||||
case ERROR_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
modalOpen: true,
|
||||
error: action.data,
|
||||
reqURL: action.url,
|
||||
reqData: action.params,
|
||||
statusCode: action.statusCode,
|
||||
connectionFailed: false,
|
||||
};
|
||||
case CONNECTION_FAILED:
|
||||
return {
|
||||
...state,
|
||||
modalOpen: true,
|
||||
error: true,
|
||||
connectionFailed: true,
|
||||
};
|
||||
|
||||
case CLOSE_MODAL:
|
||||
return {
|
||||
...state,
|
||||
modalOpen: false,
|
||||
};
|
||||
|
||||
case NOTIF_EXPANDED:
|
||||
return {
|
||||
...state,
|
||||
isNotifExpanded: action.data,
|
||||
};
|
||||
case NOTIF_MSG:
|
||||
return {
|
||||
...state,
|
||||
notifMsg: action.data,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default progressBarReducer;
|
||||
export {
|
||||
LOAD_REQUEST,
|
||||
DONE_REQUEST,
|
||||
FAILED_REQUEST,
|
||||
ERROR_REQUEST,
|
||||
CLOSE_MODAL,
|
||||
CONNECTION_FAILED,
|
||||
NOTIF_EXPANDED,
|
||||
notifExpand,
|
||||
notifMsg,
|
||||
};
|
@ -0,0 +1,75 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ProgressBar from 'react-progress-bar-plus';
|
||||
import './progress-bar.scss';
|
||||
import { NOTIF_EXPANDED } from './Actions';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
// Hide the loader once the react component is ready.
|
||||
// NOTE: This will execute only onces (since this is parent component for all other component).
|
||||
const className = document.getElementById('content').className;
|
||||
document.getElementById('content').className = className + ' show';
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
|
||||
onModalClose = () => {
|
||||
this.props.dispatch({ type: NOTIF_EXPANDED, data: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
ongoingRequest,
|
||||
percent,
|
||||
intervalTime,
|
||||
children,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div>
|
||||
{ongoingRequest && (
|
||||
<ProgressBar
|
||||
percent={percent}
|
||||
autoIncrement={true} // eslint-disable-line react/jsx-boolean-value
|
||||
intervalTime={intervalTime}
|
||||
spinner={false}
|
||||
/>
|
||||
)}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
reqURL: PropTypes.string,
|
||||
reqData: PropTypes.object,
|
||||
statusCode: PropTypes.number,
|
||||
|
||||
modalOpen: PropTypes.bool,
|
||||
error: PropTypes.object,
|
||||
ongoingRequest: PropTypes.bool,
|
||||
requestError: PropTypes.bool,
|
||||
connectionFailed: PropTypes.bool,
|
||||
|
||||
intervalTime: PropTypes.number,
|
||||
percent: PropTypes.number,
|
||||
|
||||
children: PropTypes.element,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
|
||||
isNotifExpanded: PropTypes.bool,
|
||||
notifMsg: PropTypes.string,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
...state.progressBar,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(App);
|
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, info: null };
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
// Display fallback UI
|
||||
this.setState({ hasError: true, info: info });
|
||||
// You can also log the error to an error reporting service
|
||||
// logErrorToMyService(error, info);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// You can render any custom fallback UI
|
||||
return <div>Something went wrong</div>;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBoundary.propTypes = {
|
||||
children: PropTypes.element,
|
||||
};
|
||||
|
||||
export default ErrorBoundary;
|
@ -0,0 +1,12 @@
|
||||
const defaultState = {
|
||||
percent: 0,
|
||||
intervalTime: 200,
|
||||
ongoingRequest: false,
|
||||
requestSuccess: null,
|
||||
requestError: null,
|
||||
modalOpen: false,
|
||||
error: null,
|
||||
isNotifExpanded: false,
|
||||
notifMsg: null,
|
||||
};
|
||||
export default defaultState;
|
@ -0,0 +1,79 @@
|
||||
.alertDanger
|
||||
{
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
position: -moz-sticky;
|
||||
position: -o-sticky;
|
||||
position: -ms-sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
.notifModalDialog {
|
||||
width: 800px;
|
||||
margin: 30px auto;
|
||||
right: 100px;
|
||||
top: -15px;
|
||||
}
|
||||
:global {
|
||||
@keyframes react-progress-spinner {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.react-progress-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: all 400ms;
|
||||
z-index: 9999;
|
||||
&.react-progress-bar-on-top {
|
||||
height: 100%;
|
||||
}
|
||||
&.react-progress-bar-hide {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
z-index: -10;
|
||||
}
|
||||
}
|
||||
|
||||
.react-progress-bar-percent {
|
||||
height: 2px;
|
||||
background: #e8694d;
|
||||
box-shadow: 0 2px 5px #d3291c, 0 2px 5px #d3291c;
|
||||
transition: all 200ms ease;
|
||||
}
|
||||
|
||||
.react-progress-bar-spinner {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
.react-progress-bar-spinner-left {
|
||||
left: 15px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.react-progress-bar-spinner-right {
|
||||
left: auto;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.react-progress-bar-spinner-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
box-sizing: border-box;
|
||||
border: solid 2px transparent;
|
||||
border-top-color: #d3291c;
|
||||
border-left-color: #d3291c;
|
||||
border-radius: 50%;
|
||||
animation: react-progress-spinner 400ms linear infinite;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import auth0 from "auth0-js";
|
||||
import { AUTH_CONFIG } from "./auth0-variables";
|
||||
|
||||
export default class Auth {
|
||||
auth0 = new auth0.WebAuth({
|
||||
domain: AUTH_CONFIG.domain,
|
||||
clientID: AUTH_CONFIG.clientId,
|
||||
redirectUri: AUTH_CONFIG.callbackUrl,
|
||||
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
|
||||
responseType: "token id_token",
|
||||
scope: "openid profile"
|
||||
});
|
||||
constructor() {
|
||||
this.login = this.login.bind(this);
|
||||
this.logout = this.logout.bind(this);
|
||||
this.handleAuthentication = this.handleAuthentication.bind(this);
|
||||
this.isAuthenticated = this.isAuthenticated.bind(this);
|
||||
}
|
||||
|
||||
login() {
|
||||
this.auth0.authorize();
|
||||
}
|
||||
|
||||
handleAuthentication = client => {
|
||||
this.auth0.parseHash((err, authResult) => {
|
||||
if (authResult && authResult.accessToken && authResult.idToken) {
|
||||
this.setSession(authResult);
|
||||
} else if (err) {
|
||||
console.error(err);
|
||||
window.location.replace("/graphql/graphiql");
|
||||
alert(`Error: ${err.error}. Check the console for further details.`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setSession(authResult) {
|
||||
// Set the time that the access token will expire at
|
||||
let expiresAt = JSON.stringify(
|
||||
authResult.expiresIn * 1000 + new Date().getTime()
|
||||
);
|
||||
localStorage.setItem("auth0:access_token", authResult.accessToken);
|
||||
localStorage.setItem("auth0:id_token", authResult.idToken);
|
||||
localStorage.setItem("auth0:expires_at", expiresAt);
|
||||
localStorage.setItem("auth0:id_token:sub", authResult.idTokenPayload.sub);
|
||||
// navigate to the home route
|
||||
window.location.replace("/graphql/graphiql");
|
||||
}
|
||||
|
||||
logout() {
|
||||
// Clear access token and ID token from local storage
|
||||
localStorage.removeItem("auth0:access_token");
|
||||
localStorage.removeItem("auth0:id_token");
|
||||
localStorage.removeItem("auth0:expires_at");
|
||||
localStorage.removeItem("auth0:id_token:sub");
|
||||
// navigate to the home route
|
||||
// history.replace("/home");
|
||||
window.location.replace("/graphql/graphiql");
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
// Check whether the current time is past the
|
||||
// access token's expiry time
|
||||
let expiresAt = JSON.parse(localStorage.getItem("auth0:expires_at"));
|
||||
return new Date().getTime() < expiresAt;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { authDomain, authClientId, callbackUrl } from "./constants";
|
||||
|
||||
export const AUTH_CONFIG = {
|
||||
domain: authDomain,
|
||||
clientId: authClientId,
|
||||
callbackUrl: callbackUrl
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
export const GRAPHQL_URL =
|
||||
"https://learn.hasura.io/graphql";
|
||||
export const REALTIME_GRAPHQL_URL =
|
||||
"wss://learn.hasura.io/graphql";
|
||||
export const authClientId = "P38qnFo1lFAQJrzkun--wEzqljVNGcWW";
|
||||
export const authDomain = "graphql-tutorials.auth0.com";
|
||||
export const callbackUrl = window.__env.callbackUrl;
|
@ -0,0 +1,3 @@
|
||||
import createHistory from "history/createBrowserHistory";
|
||||
|
||||
export default createHistory();
|
@ -0,0 +1,33 @@
|
||||
import React, { Component } from "react";
|
||||
import loading from "./loading.svg";
|
||||
|
||||
class Callback extends Component {
|
||||
componentDidMount() {
|
||||
console.log(this.props);
|
||||
if (/access_token|id_token|error/.test(this.props.location.hash)) {
|
||||
this.props.route.auth.handleAuthentication();
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const style = {
|
||||
position: "absolute",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: "white"
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<img src={loading} alt="loading" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Callback;
|
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg width='120px' height='120px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><defs><filter id="uil-ring-shadow" x="-100%" y="-100%" width="300%" height="300%"><feOffset result="offOut" in="SourceGraphic" dx="0" dy="0"></feOffset><feGaussianBlur result="blurOut" in="offOut" stdDeviation="0"></feGaussianBlur><feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend></filter></defs><path d="M10,50c0,0,0,0.5,0.1,1.4c0,0.5,0.1,1,0.2,1.7c0,0.3,0.1,0.7,0.1,1.1c0.1,0.4,0.1,0.8,0.2,1.2c0.2,0.8,0.3,1.8,0.5,2.8 c0.3,1,0.6,2.1,0.9,3.2c0.3,1.1,0.9,2.3,1.4,3.5c0.5,1.2,1.2,2.4,1.8,3.7c0.3,0.6,0.8,1.2,1.2,1.9c0.4,0.6,0.8,1.3,1.3,1.9 c1,1.2,1.9,2.6,3.1,3.7c2.2,2.5,5,4.7,7.9,6.7c3,2,6.5,3.4,10.1,4.6c3.6,1.1,7.5,1.5,11.2,1.6c4-0.1,7.7-0.6,11.3-1.6 c3.6-1.2,7-2.6,10-4.6c3-2,5.8-4.2,7.9-6.7c1.2-1.2,2.1-2.5,3.1-3.7c0.5-0.6,0.9-1.3,1.3-1.9c0.4-0.6,0.8-1.3,1.2-1.9 c0.6-1.3,1.3-2.5,1.8-3.7c0.5-1.2,1-2.4,1.4-3.5c0.3-1.1,0.6-2.2,0.9-3.2c0.2-1,0.4-1.9,0.5-2.8c0.1-0.4,0.1-0.8,0.2-1.2 c0-0.4,0.1-0.7,0.1-1.1c0.1-0.7,0.1-1.2,0.2-1.7C90,50.5,90,50,90,50s0,0.5,0,1.4c0,0.5,0,1,0,1.7c0,0.3,0,0.7,0,1.1 c0,0.4-0.1,0.8-0.1,1.2c-0.1,0.9-0.2,1.8-0.4,2.8c-0.2,1-0.5,2.1-0.7,3.3c-0.3,1.2-0.8,2.4-1.2,3.7c-0.2,0.7-0.5,1.3-0.8,1.9 c-0.3,0.7-0.6,1.3-0.9,2c-0.3,0.7-0.7,1.3-1.1,2c-0.4,0.7-0.7,1.4-1.2,2c-1,1.3-1.9,2.7-3.1,4c-2.2,2.7-5,5-8.1,7.1 c-0.8,0.5-1.6,1-2.4,1.5c-0.8,0.5-1.7,0.9-2.6,1.3L66,87.7l-1.4,0.5c-0.9,0.3-1.8,0.7-2.8,1c-3.8,1.1-7.9,1.7-11.8,1.8L47,90.8 c-1,0-2-0.2-3-0.3l-1.5-0.2l-0.7-0.1L41.1,90c-1-0.3-1.9-0.5-2.9-0.7c-0.9-0.3-1.9-0.7-2.8-1L34,87.7l-1.3-0.6 c-0.9-0.4-1.8-0.8-2.6-1.3c-0.8-0.5-1.6-1-2.4-1.5c-3.1-2.1-5.9-4.5-8.1-7.1c-1.2-1.2-2.1-2.7-3.1-4c-0.5-0.6-0.8-1.4-1.2-2 c-0.4-0.7-0.8-1.3-1.1-2c-0.3-0.7-0.6-1.3-0.9-2c-0.3-0.7-0.6-1.3-0.8-1.9c-0.4-1.3-0.9-2.5-1.2-3.7c-0.3-1.2-0.5-2.3-0.7-3.3 c-0.2-1-0.3-2-0.4-2.8c-0.1-0.4-0.1-0.8-0.1-1.2c0-0.4,0-0.7,0-1.1c0-0.7,0-1.2,0-1.7C10,50.5,10,50,10,50z" fill="#337ab7" filter="url(#uil-ring-shadow)"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" repeatCount="indefinite" dur="1s"></animateTransform></path></svg>
|
After Width: | Height: | Size: 2.2 KiB |
@ -0,0 +1,14 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
class BreadCrumb extends Component {
|
||||
render() {
|
||||
const styles = require('./BreadCrumb.scss');
|
||||
return <div className={styles.breadcrumbWrapper}>{this.props.data}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
BreadCrumb.propTypes = {
|
||||
data: React.PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default BreadCrumb;
|
@ -0,0 +1,9 @@
|
||||
@import "../Common.scss";
|
||||
|
||||
.breadcrumbWrapper {
|
||||
padding: 20px 0px;
|
||||
padding-bottom: 0px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #767E93;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
@import "./Common.scss";
|
||||
button.control {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.subTopic {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.readyIcon {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.failIcon {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.eventContainer {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.logContainer {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
table td.longOne {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.floatRight {
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
const Spinner = () => {
|
||||
const styles = require('./Spinner.scss');
|
||||
|
||||
return (
|
||||
<div className={styles.sk_circle}>
|
||||
<div className={styles.sk_circle1 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle2 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle3 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle4 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle5 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle6 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle7 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle8 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle9 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle10 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle11 + ' ' + styles.sk_child} />
|
||||
<div className={styles.sk_circle12 + ' ' + styles.sk_child} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Spinner;
|
@ -0,0 +1,121 @@
|
||||
.sk_circle {
|
||||
margin: 100px auto;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
}
|
||||
.sk_circle .sk_child {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.sk_circle .sk_child:before {
|
||||
content: '';
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 15%;
|
||||
height: 15%;
|
||||
background-color: #333;
|
||||
border-radius: 100%;
|
||||
-webkit-animation: sk_circleBounceDelay 1.2s infinite ease-in-out both;
|
||||
animation: sk_circleBounceDelay 1.2s infinite ease-in-out both;
|
||||
}
|
||||
.sk_circle .sk_circle2 {
|
||||
-webkit-transform: rotate(30deg);
|
||||
-ms-transform: rotate(30deg);
|
||||
transform: rotate(30deg); }
|
||||
.sk_circle .sk_circle3 {
|
||||
-webkit-transform: rotate(60deg);
|
||||
-ms-transform: rotate(60deg);
|
||||
transform: rotate(60deg); }
|
||||
.sk_circle .sk_circle4 {
|
||||
-webkit-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
transform: rotate(90deg); }
|
||||
.sk_circle .sk_circle5 {
|
||||
-webkit-transform: rotate(120deg);
|
||||
-ms-transform: rotate(120deg);
|
||||
transform: rotate(120deg); }
|
||||
.sk_circle .sk_circle6 {
|
||||
-webkit-transform: rotate(150deg);
|
||||
-ms-transform: rotate(150deg);
|
||||
transform: rotate(150deg); }
|
||||
.sk_circle .sk_circle7 {
|
||||
-webkit-transform: rotate(180deg);
|
||||
-ms-transform: rotate(180deg);
|
||||
transform: rotate(180deg); }
|
||||
.sk_circle .sk_circle8 {
|
||||
-webkit-transform: rotate(210deg);
|
||||
-ms-transform: rotate(210deg);
|
||||
transform: rotate(210deg); }
|
||||
.sk_circle .sk_circle9 {
|
||||
-webkit-transform: rotate(240deg);
|
||||
-ms-transform: rotate(240deg);
|
||||
transform: rotate(240deg); }
|
||||
.sk_circle .sk_circle10 {
|
||||
-webkit-transform: rotate(270deg);
|
||||
-ms-transform: rotate(270deg);
|
||||
transform: rotate(270deg); }
|
||||
.sk_circle .sk_circle11 {
|
||||
-webkit-transform: rotate(300deg);
|
||||
-ms-transform: rotate(300deg);
|
||||
transform: rotate(300deg); }
|
||||
.sk_circle .sk_circle12 {
|
||||
-webkit-transform: rotate(330deg);
|
||||
-ms-transform: rotate(330deg);
|
||||
transform: rotate(330deg); }
|
||||
.sk_circle .sk_circle2:before {
|
||||
-webkit-animation-delay: -1.1s;
|
||||
animation-delay: -1.1s; }
|
||||
.sk_circle .sk_circle3:before {
|
||||
-webkit-animation-delay: -1s;
|
||||
animation-delay: -1s; }
|
||||
.sk_circle .sk_circle4:before {
|
||||
-webkit-animation-delay: -0.9s;
|
||||
animation-delay: -0.9s; }
|
||||
.sk_circle .sk_circle5:before {
|
||||
-webkit-animation-delay: -0.8s;
|
||||
animation-delay: -0.8s; }
|
||||
.sk_circle .sk_circle6:before {
|
||||
-webkit-animation-delay: -0.7s;
|
||||
animation-delay: -0.7s; }
|
||||
.sk_circle .sk_circle7:before {
|
||||
-webkit-animation-delay: -0.6s;
|
||||
animation-delay: -0.6s; }
|
||||
.sk_circle .sk_circle8:before {
|
||||
-webkit-animation-delay: -0.5s;
|
||||
animation-delay: -0.5s; }
|
||||
.sk_circle .sk_circle9:before {
|
||||
-webkit-animation-delay: -0.4s;
|
||||
animation-delay: -0.4s; }
|
||||
.sk_circle .sk_circle10:before {
|
||||
-webkit-animation-delay: -0.3s;
|
||||
animation-delay: -0.3s; }
|
||||
.sk_circle .sk_circle11:before {
|
||||
-webkit-animation-delay: -0.2s;
|
||||
animation-delay: -0.2s; }
|
||||
.sk_circle .sk_circle12:before {
|
||||
-webkit-animation-delay: -0.1s;
|
||||
animation-delay: -0.1s; }
|
||||
|
||||
@-webkit-keyframes sk_circleBounceDelay {
|
||||
0%, 80%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
} 40% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk_circleBounceDelay {
|
||||
0%, 80%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
} 40% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Raleway:400,600');
|
||||
@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400');
|
||||
body {
|
||||
font-family: 'Open Sans';
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loginTextbox {
|
||||
font-size: 16px;
|
||||
height: 43px;
|
||||
width: 74%;
|
||||
margin-right: 1%;
|
||||
font-weight: 300;
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 5px;
|
||||
padding: 0 0 0 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.loginButton {
|
||||
height: 45px;
|
||||
width: 10%;
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background-color: green !important;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
margin-right: 1%;
|
||||
}
|
||||
|
||||
.loginButton:hover {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
.loginHeading {
|
||||
text-align: center;
|
||||
font-family: 'Raleway';
|
||||
margin-top: 0;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.loginWrapper {
|
||||
width: 600px;
|
||||
padding: 30px;
|
||||
margin: 0 auto;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 5px;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
.footerWrapper {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.apiHasura {
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.apiHasura i {
|
||||
color: #757575;;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.apiHasura i:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.built {
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.built i {
|
||||
color: #f93c18;
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Helmet from 'react-helmet';
|
||||
import './App.css';
|
||||
import { updateGraphQLEndpoint } from '../ApiExplorer/Actions';
|
||||
|
||||
class LoginComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { graphqlEndpoint: '' };
|
||||
}
|
||||
setGraphQLEndpoint(e) {
|
||||
this.setState({ ...this.state, graphqlEndpoint: e.target.value });
|
||||
}
|
||||
render() {
|
||||
const { dispatch } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div className="loginWrapper">
|
||||
<Helmet
|
||||
title="GraphiQL Online with Headers | Built by Hasura"
|
||||
description="An online version of GraphiQL. Manage headers easily. Test your GraphQL servers."
|
||||
/>
|
||||
<h2 className="loginHeading"> Online GraphiQL </h2>
|
||||
<div className="login">
|
||||
<div>
|
||||
<form>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
className="loginTextbox"
|
||||
placeholder="Enter GraphQL Endpoint URL"
|
||||
onChange={this.setGraphQLEndpoint.bind(this)}
|
||||
/>
|
||||
<button
|
||||
className="loginButton"
|
||||
type="submit"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
const emailRegex = /^(http[s]?:\/\/){0,1}(www\.){0,1}[a-zA-Z0-9\.\-]+\.[a-zA-Z]{2,5}[\.]{0,1}/;
|
||||
if (!emailRegex.test(this.state.graphqlEndpoint)) {
|
||||
alert('Please enter a valid URL');
|
||||
} else {
|
||||
dispatch(updateGraphQLEndpoint(this.state.graphqlEndpoint));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className={'fa fa-sign-in'} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footerWrapper">
|
||||
<div className="built">
|
||||
Built with <i className="fa fa-heart" /> by <a href={'http://hasura.io/'} target={'_blank'}>Hasura</a>
|
||||
</div>
|
||||
<div className="apiHasura">
|
||||
<a href="https://github.com/hasura/graphql-engine/tree/master/community/tools/graphiql-online" target={'_blank'}>
|
||||
<i className="fa fa-github" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LoginComponent.propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default LoginComponent;
|
@ -0,0 +1,12 @@
|
||||
import LoginComponent from './Login';
|
||||
|
||||
const generatedLoginComponent = connect => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
...state.apiexplorer,
|
||||
};
|
||||
};
|
||||
return connect(mapStateToProps)(LoginComponent);
|
||||
};
|
||||
|
||||
export default generatedLoginComponent;
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Export component modules
|
||||
*/
|
||||
|
||||
export App from './App/App';
|
||||
export PageNotFound from './404/PageNotFound';
|
@ -0,0 +1,24 @@
|
||||
/* @flow */
|
||||
const appConfig = require('../appconfig');
|
||||
|
||||
const host = appConfig.appHost;
|
||||
const port = appConfig.port[process.env.NODE_ENV || 'development'];
|
||||
|
||||
// require('babel-polyfill');
|
||||
|
||||
const environment = {
|
||||
development: {
|
||||
isProduction: false,
|
||||
},
|
||||
production: {
|
||||
isProduction: true,
|
||||
},
|
||||
}[process.env.NODE_ENV || 'development'];
|
||||
|
||||
module.exports = Object.assign(
|
||||
{
|
||||
host: host,
|
||||
port: port,
|
||||
},
|
||||
environment
|
||||
);
|
@ -0,0 +1,107 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Helmet from 'react-helmet';
|
||||
/**
|
||||
* Wrapper component containing HTML metadata and boilerplate tags.
|
||||
* Used in server-side code only to wrap the string output of the
|
||||
* rendered route component.
|
||||
*
|
||||
* The only thing this component doesn't (and can't) include is the
|
||||
* HTML doctype declaration, which is added to the rendered output
|
||||
* by the server.js file.
|
||||
*/
|
||||
export default class Html extends Component {
|
||||
static propTypes = {
|
||||
assets: PropTypes.object,
|
||||
component: PropTypes.node,
|
||||
baseDomain: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { assets } = this.props;
|
||||
const head = Helmet.rewind();
|
||||
|
||||
return (
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<link rel="icon" type="image/png" href="/rstatic/favicon.png" />
|
||||
<title>GraphiQL Online with Headers | Built by Hasura</title>
|
||||
<meta name="title" content="GraphiQL Online with Headers | Built by Hasura" />
|
||||
<meta name="description" content="An online version of GraphiQL. Manage headers easily. Test your GraphQL servers" />
|
||||
|
||||
{Object.keys(assets.styles).map((style, key) => (
|
||||
<link
|
||||
href={assets.styles[style]}
|
||||
key={key}
|
||||
media="screen, projection"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
charSet="UTF-8"
|
||||
/>
|
||||
))}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
|
||||
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `window.__env={
|
||||
graphqlEndpoint: '${process.env.GRAPHQL_ENDPOINT}',
|
||||
query: '${process.env.QUERY_STRING}',
|
||||
headers: '${process.env.HEADER_STRING}',
|
||||
variables: '${process.env.VARIABLE_STRING}',
|
||||
callbackUrl: '${process.env.REACT_APP_CALLBACK_URL}',
|
||||
environment: '${process.env.NODE_ENV}'
|
||||
};`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
.content {
|
||||
display: 'none';
|
||||
opacity: 0;
|
||||
transition: opacity .20s linear;
|
||||
}
|
||||
.content.show {
|
||||
display: 'block';
|
||||
opacity: 1;
|
||||
transition: opacity .20s linear;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
id="loading"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `<div class="page-loading" style="
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: sans-serif;
|
||||
justify-content: center;
|
||||
">
|
||||
<span class="" style="
|
||||
font-size: 2em;
|
||||
margin-top: -3em;
|
||||
color: #848484;
|
||||
">
|
||||
Loading...
|
||||
</span>
|
||||
</div>`,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div id="content" className="content" />
|
||||
<script src={assets.javascript.main} charSet="UTF-8" />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Return the status code from the last matched route with a status property.
|
||||
*
|
||||
* @param matchedRoutes
|
||||
* @returns {Number|null}
|
||||
*/
|
||||
export default matchedRoutes => {
|
||||
return matchedRoutes.reduce((prev, cur) => cur.status || prev, null);
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import { createRoutes } from 'react-router/lib/RouteUtils';
|
||||
|
||||
// Wrap the hooks so they don't fire if they're called before
|
||||
// the store is initialised. This only happens when doing the first
|
||||
// client render of a route that has an onEnter hook
|
||||
function makeHooksSafe(routes, store) {
|
||||
if (Array.isArray(routes)) {
|
||||
return routes.map(route => makeHooksSafe(route, store));
|
||||
}
|
||||
|
||||
const onEnter = routes.onEnter;
|
||||
|
||||
if (onEnter) {
|
||||
routes.onEnter = function safeOnEnter(...args) {
|
||||
try {
|
||||
store.getState();
|
||||
} catch (err) {
|
||||
if (onEnter.length === 3) {
|
||||
args[2]();
|
||||
}
|
||||
|
||||
// There's no store yet so ignore the hook
|
||||
return;
|
||||
}
|
||||
|
||||
onEnter.apply(null, args);
|
||||
};
|
||||
}
|
||||
|
||||
if (routes.childRoutes) {
|
||||
makeHooksSafe(routes.childRoutes, store);
|
||||
}
|
||||
|
||||
if (routes.indexRoute) {
|
||||
makeHooksSafe(routes.indexRoute, store);
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
export default function makeRouteHooksSafe(_getRoutes) {
|
||||
return store => makeHooksSafe(createRoutes(_getRoutes(store)), store);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { routerReducer } from 'react-router-redux';
|
||||
import apiExplorerReducer from 'components/ApiExplorer/Actions';
|
||||
import progressBarReducer from 'components/App/Actions';
|
||||
|
||||
const reducer = combineReducers({
|
||||
progressBar: progressBarReducer,
|
||||
apiexplorer: apiExplorerReducer,
|
||||
routing: routerReducer,
|
||||
});
|
||||
|
||||
export default reducer;
|
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { Route, IndexRoute } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import { App, PageNotFound } from 'components';
|
||||
import generatedApiExplorer from './components/ApiExplorer/ApiExplorerGenerator';
|
||||
import Auth from './components/Auth/Auth';
|
||||
import Callback from './components/Callback/Callback';
|
||||
|
||||
const auth = new Auth();
|
||||
|
||||
const routes = () => {
|
||||
// loads schema
|
||||
return (
|
||||
<Route path="/" component={App} auth={auth}>
|
||||
{/*
|
||||
<Route path="">
|
||||
<IndexRoute component={generatedLoginComponent(connect)} />
|
||||
</Route>
|
||||
*/}
|
||||
<Route path="">
|
||||
<IndexRoute auth={auth} component={generatedApiExplorer(connect)} />
|
||||
</Route>
|
||||
<Route path="404" component={PageNotFound} status="404" />
|
||||
<Route exact path="/callback" auth={auth} component={Callback} />
|
||||
<Route path="*" component={PageNotFound} status="404" />
|
||||
</Route>
|
||||
);
|
||||
};
|
||||
|
||||
export default routes;
|
59
community/learn/graphql-tutorials/backend-services/graphiql/src/server.js
Executable file
@ -0,0 +1,59 @@
|
||||
import Express from 'express';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/server';
|
||||
import config from './config';
|
||||
// import favicon from 'serve-favicon';
|
||||
import path from 'path';
|
||||
import Html from './helpers/Html';
|
||||
import http from 'http';
|
||||
|
||||
global.__DISABLE_SSR__ = true;
|
||||
|
||||
// Express middleware
|
||||
const app = new Express();
|
||||
const server = new http.Server(app);
|
||||
|
||||
/* Constants */
|
||||
/* Routes and middleware */
|
||||
// app.use(favicon(path.join(__dirname, '..', 'static', 'favicon.ico')));
|
||||
app.use('/rstatic', Express.static(path.join(__dirname, '..', 'static')));
|
||||
|
||||
app.use((req, res) => {
|
||||
if (__DEVELOPMENT__) {
|
||||
// Do not cache webpack stats: the script file would change since
|
||||
// hot module replacement is enabled in the development env
|
||||
webpackIsomorphicTools.refresh();
|
||||
}
|
||||
|
||||
// Initialize the store here
|
||||
function hydrateOnClient() {
|
||||
res.send(
|
||||
'<!doctype html>\n' +
|
||||
ReactDOM.renderToString(
|
||||
<Html assets={webpackIsomorphicTools.assets()} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (__DISABLE_SSR__) {
|
||||
hydrateOnClient();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (config.port) {
|
||||
server.listen(config.port, config.host, err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
console.info(
|
||||
'==> 💻 Open http://%s:%s in a browser to view the app.',
|
||||
config.host,
|
||||
config.port
|
||||
);
|
||||
});
|
||||
} else {
|
||||
console.error(
|
||||
'==> ERROR: No PORT environment variable has been specified'
|
||||
);
|
||||
}
|
71
community/learn/graphql-tutorials/backend-services/graphiql/src/theme/bootstrap.config.js
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Bootstrap configuration for bootstrap-sass-loader
|
||||
*
|
||||
* Scripts are disabled to not load jQuery.
|
||||
* If you depend on Bootstrap scripts consider react-bootstrap instead.
|
||||
* https://github.com/react-bootstrap/react-bootstrap
|
||||
*
|
||||
* In order to keep the bundle size low in production
|
||||
* disable components you don't use.
|
||||
*
|
||||
*/
|
||||
module.exports = {
|
||||
preBootstrapCustomizations: './src/theme/variables.scss',
|
||||
mainSass: './src/theme/bootstrap.overrides.scss',
|
||||
verbose: false,
|
||||
debug: false,
|
||||
scripts: {
|
||||
transition: false,
|
||||
alert: false,
|
||||
button: true,
|
||||
carousel: false,
|
||||
collapse: false,
|
||||
dropdown: true,
|
||||
modal: true,
|
||||
tooltip: false,
|
||||
popover: false,
|
||||
scrollspy: false,
|
||||
tab: false,
|
||||
affix: false,
|
||||
},
|
||||
styles: {
|
||||
mixins: true,
|
||||
normalize: true,
|
||||
print: true,
|
||||
glyphicons: true,
|
||||
scaffolding: true,
|
||||
type: true,
|
||||
code: true,
|
||||
grid: true,
|
||||
tables: true,
|
||||
forms: true,
|
||||
buttons: true,
|
||||
'component-animations': true,
|
||||
dropdowns: true,
|
||||
'button-groups': true,
|
||||
'input-groups': true,
|
||||
navs: true,
|
||||
navbar: true,
|
||||
breadcrumbs: true,
|
||||
pagination: true,
|
||||
pager: true,
|
||||
labels: true,
|
||||
badges: true,
|
||||
jumbotron: true,
|
||||
thumbnails: true,
|
||||
alerts: true,
|
||||
'progress-bars': true,
|
||||
media: true,
|
||||
'list-group': true,
|
||||
panels: true,
|
||||
wells: true,
|
||||
'responsive-embed': true,
|
||||
close: true,
|
||||
modals: true,
|
||||
tooltip: true,
|
||||
popovers: true,
|
||||
carousel: false,
|
||||
utilities: true,
|
||||
'responsive-utilities': true,
|
||||
},
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
const bootstrapConfig = require('./bootstrap.config.js');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
|
||||
bootstrapConfig.styleLoaders = ExtractTextPlugin.extract({
|
||||
fallback: 'style-loader',
|
||||
use: ['css-loader', 'sass-loader'],
|
||||
});
|
||||
module.exports = bootstrapConfig;
|
198
community/learn/graphql-tutorials/backend-services/graphiql/src/theme/bootstrap.overrides.scss
vendored
Normal file
@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Override Bootstrap styles that you can't modify via variables here.
|
||||
*
|
||||
*/
|
||||
input[type="radio"], input[type="checkbox"]
|
||||
{
|
||||
margin: 0 5px 0px 0px;
|
||||
}
|
||||
label
|
||||
{
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.navbar-brand {
|
||||
position: relative;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.graphiql-container .topBar {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-nav > .active > a,
|
||||
.navbar-default .navbar-nav > .active > a:hover,
|
||||
.navbar-default .navbar-nav > .active > a:focus {
|
||||
color: #33e0ff;
|
||||
background-color: transparent;
|
||||
}
|
||||
.control-label
|
||||
{
|
||||
padding-top: 0;
|
||||
}
|
||||
form {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
.form-control:focus {
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0px rgba(102, 175, 233, 0.6);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
// border-radius: 0;
|
||||
height: auto;
|
||||
}
|
||||
select.form-control
|
||||
{
|
||||
border-radius: 0;
|
||||
-webkit-border-radius: 0px;
|
||||
}
|
||||
.btn {
|
||||
// border-radius: 0;
|
||||
outline: none;
|
||||
}
|
||||
.btn:active, .btn.active
|
||||
{
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.btn:focus, .btn.focus, .btn:active:focus, .btn:active.focus, .btn.active:focus, .btn.active.focus
|
||||
{
|
||||
outline: none !important;
|
||||
}
|
||||
.btn:focus
|
||||
{
|
||||
outline: none !important;
|
||||
}
|
||||
.alert {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: #FEC53D !important;
|
||||
border-color: #FEC53D !important;
|
||||
color: #000 !important;
|
||||
padding: 5px 10px !important;
|
||||
font-size: 12px !important;
|
||||
line-height: 1.5 !important;
|
||||
}
|
||||
.btn-warning:hover {
|
||||
color: #000;
|
||||
background-color: #ec971f;
|
||||
border-color: #d58512;
|
||||
}
|
||||
// Generate Code modal
|
||||
.padd_remove
|
||||
{
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
.generateCodeWrapper
|
||||
{
|
||||
width: 65% !important;
|
||||
.modal-content
|
||||
{
|
||||
.modal-header
|
||||
{
|
||||
background-color: #F8FAFB;
|
||||
color: #222 ;
|
||||
padding: 20px;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
h4
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
button
|
||||
{
|
||||
text-shadow: none;
|
||||
color: #C4C6C6;
|
||||
opacity: 1;
|
||||
span
|
||||
{
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal-body
|
||||
{
|
||||
padding: 25px;
|
||||
.generateBodyWrapper
|
||||
{
|
||||
.generateButtonWrapper
|
||||
{
|
||||
width: 100%;
|
||||
float: left;
|
||||
padding-bottom: 20px;
|
||||
.generateSelect
|
||||
{
|
||||
button
|
||||
{
|
||||
width: auto;
|
||||
padding: 10px 35px;
|
||||
padding-left: 10px;
|
||||
background-color: #F9F9F9;
|
||||
color: #606060;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
border: 1px solid #ccc;
|
||||
position: relative;
|
||||
.dropCaret
|
||||
{
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.generateCopyButton
|
||||
{
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
button {
|
||||
background-color: #FEC53D;
|
||||
border-radius: 5px;
|
||||
color: #606060;
|
||||
border: 1px solid #FEC53D;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
&:hover {
|
||||
background-color: #F2B130;
|
||||
}
|
||||
}
|
||||
}
|
||||
.generateCopyButtonDisabled
|
||||
{
|
||||
text-align: right;
|
||||
button
|
||||
{
|
||||
border-radius: 5px;
|
||||
border: 1px solid #606060;
|
||||
height: 39px;
|
||||
padding: 5px 15px;
|
||||
font-size: 15px;
|
||||
background-color: #FFF;
|
||||
text-align: center;
|
||||
color: #606060;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.ace_editor
|
||||
{
|
||||
min-height: 150px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.ace-github
|
||||
{
|
||||
min-height: 150px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Configuration file for font-awesome-webpack
|
||||
*
|
||||
* In order to keep the bundle size low in production,
|
||||
* disable components you don't use.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
styles: {
|
||||
mixins: true,
|
||||
core: true,
|
||||
icons: true,
|
||||
larger: true,
|
||||
path: true,
|
||||
animated: true,
|
||||
},
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Configuration file for font-awesome-webpack
|
||||
*
|
||||
*/
|
||||
|
||||
// Example:
|
||||
// @fa-border-color: #ddd;
|
@ -0,0 +1,8 @@
|
||||
const fontAwesomeConfig = require('./font-awesome.config.js');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
|
||||
fontAwesomeConfig.styleLoader = ExtractTextPlugin.extract({
|
||||
fallback: 'style-loader',
|
||||
use: 'css-loader!less-loader',
|
||||
});
|
||||
module.exports = fontAwesomeConfig;
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Define scss variables here.
|
||||
*
|
||||
* Available options for Bootstrap:
|
||||
* http://getbootstrap.com/customize/
|
||||
*
|
||||
*/
|
||||
|
||||
// Custom Colors
|
||||
$cyan: #33e0ff;
|
||||
$humility: #777;
|
||||
|
||||
// Bootstrap Variables
|
||||
$brand-primary: darken(#428bca, 6.5%);
|
||||
$brand-secondary: #e25139;
|
||||
$brand-success: #5cb85c;
|
||||
$brand-warning: #f0ad4e;
|
||||
$brand-danger: #d9534f;
|
||||
$brand-info: #5bc0de;
|
||||
|
||||
$text-color: #333;
|
||||
|
||||
$font-size-base: 14px;
|
||||
$font-family-sans-serif: "Helvetica Neue", Helvetica, sans-serif;
|
||||
|
||||
$table-bg-hover: #EBF7DE !default;
|
||||
$table-bg-active: $table-bg-hover !default;
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,65 @@
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-59768903-1"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-59768903-1');
|
||||
</script>
|
||||
<link rel="icon" type="image/png" href="./favicon.png" />
|
||||
<title>GraphiQL Online with Headers | Built by Hasura</title>
|
||||
<meta name="title" content="GraphiQL Online with Headers | Built by Hasura" />
|
||||
<meta name="description" content="An online version of GraphiQL. Manage headers easily. Test your GraphQL servers" />
|
||||
<script>
|
||||
window.__env = {
|
||||
graphqlEndpoint: 'https://learn.hasura.io/graphql',
|
||||
callbackUrl: 'https://learn.hasura.io/graphql/graphiql/callback',
|
||||
headers: '',
|
||||
variables: '',
|
||||
query: ''
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
.mainContent {
|
||||
display: 'none';
|
||||
opacity: 0;
|
||||
transition: opacity .20s linear;
|
||||
}
|
||||
.mainContent.show {
|
||||
display: 'block';
|
||||
opacity: 1;
|
||||
transition: opacity .20s linear;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="loading">
|
||||
<div class="page-loading" style="
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: sans-serif;
|
||||
justify-content: center;
|
||||
">
|
||||
<span class="" style="
|
||||
font-size: 2em;
|
||||
margin-top: -3em;
|
||||
color: #848484;
|
||||
">
|
||||
Loading...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content" class="mainContent"></div>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>
|
||||
|
||||
<link rel="stylesheet" href="https://storage.googleapis.com/graphql-engine-cdn.hasura.io/learn-hasura/graphiql/main.css" charset="UTF-8"/>
|
||||
<script src="https://storage.googleapis.com/graphql-engine-cdn.hasura.io/learn-hasura/graphiql/vendor.js" charset="UTF-8"></script>
|
||||
<script src="https://storage.googleapis.com/graphql-engine-cdn.hasura.io/learn-hasura/graphiql/main.js" charset="UTF-8"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,116 @@
|
||||
---
|
||||
# Output debugging info
|
||||
# loglevel: debug
|
||||
|
||||
# Major version of Bootstrap: 3 or 4
|
||||
bootstrapVersion: 3
|
||||
|
||||
# If Bootstrap version 3 is used - turn on/off custom icon font path
|
||||
useCustomIconFontPath: false
|
||||
|
||||
# Webpack loaders, order matters
|
||||
styleLoaders:
|
||||
- style-loader
|
||||
- css-loader
|
||||
- sass-loader
|
||||
|
||||
# Extract styles to stand-alone css file
|
||||
# Different settings for different environments can be used,
|
||||
# It depends on value of NODE_ENV environment variable
|
||||
# This param can also be set in webpack config:
|
||||
# entry: 'bootstrap-loader/extractStyles'
|
||||
extractStyles: true
|
||||
# env:
|
||||
# development:
|
||||
# extractStyles: false
|
||||
# production:
|
||||
# extractStyles: true
|
||||
|
||||
# Customize Bootstrap variables that get imported before the original Bootstrap variables.
|
||||
# Thus original Bootstrap variables can depend on values from here. All the bootstrap
|
||||
# variables are configured with !default, and thus, if you define the variable here, then
|
||||
# that value is used, rather than the default. However, many bootstrap variables are derived
|
||||
# from other bootstrap variables, and thus, you want to set this up before we load the
|
||||
# official bootstrap versions.
|
||||
# For example, _variables.scss contains:
|
||||
# $input-color: $gray !default;
|
||||
# This means you can define $input-color before we load _variables.scss
|
||||
|
||||
#preBootstrapCustomizations: ./src/theme/variables.scss
|
||||
|
||||
# This gets loaded after bootstrap/variables is loaded and before bootstrap is loaded.
|
||||
# A good example of this is when you want to override a bootstrap variable to be based
|
||||
# on the default value of bootstrap. This is pretty specialized case. Thus, you normally
|
||||
# just override bootrap variables in preBootstrapCustomizations so that derived
|
||||
# variables will use your definition.
|
||||
#
|
||||
# For example, in _variables.scss:
|
||||
# $input-height: (($font-size-base * $line-height) + ($input-padding-y * 2) + ($border-width * 2)) !default;
|
||||
# This means that you could define this yourself in preBootstrapCustomizations. Or you can do
|
||||
# this in bootstrapCustomizations to make the input height 10% bigger than the default calculation.
|
||||
# Thus you can leverage the default calculations.
|
||||
# $input-height: $input-height * 1.10;
|
||||
|
||||
# bootstrapCustomizations: ./app/styles/bootstrap/customizations.scss
|
||||
|
||||
# Import your custom styles here. You have access to all the bootstrap variables. If you require
|
||||
# your sass files separately, you will not have access to the bootstrap variables, mixins, clases, etc.
|
||||
# Usually this endpoint-file contains list of @imports of your application styles.
|
||||
|
||||
#appStyles: ./src/theme/bootstrap.overrides.scss
|
||||
|
||||
### Bootstrap styles
|
||||
styles:
|
||||
|
||||
# Mixins
|
||||
mixins: true
|
||||
|
||||
# Reset and dependencies
|
||||
normalize: true
|
||||
print: true
|
||||
glyphicons: true
|
||||
|
||||
# Core CSS
|
||||
scaffolding: true
|
||||
type: true
|
||||
code: true
|
||||
grid: true
|
||||
tables: true
|
||||
forms: true
|
||||
buttons: true
|
||||
|
||||
# Components
|
||||
component-animations: true
|
||||
dropdowns: true
|
||||
button-groups: true
|
||||
input-groups: true
|
||||
navs: true
|
||||
navbar: true
|
||||
breadcrumbs: true
|
||||
pagination: true
|
||||
pager: true
|
||||
labels: true
|
||||
badges: true
|
||||
jumbotron: true
|
||||
thumbnails: true
|
||||
alerts: true
|
||||
progress-bars: true
|
||||
media: true
|
||||
list-group: true
|
||||
panels: true
|
||||
wells: true
|
||||
responsive-embed: true
|
||||
close: true
|
||||
|
||||
# Components w/ JavaScript
|
||||
modals: true
|
||||
tooltip: true
|
||||
popovers: true
|
||||
carousel: true
|
||||
|
||||
# Utility classes
|
||||
utilities: true
|
||||
responsive-utilities: true
|
||||
|
||||
### Bootstrap scripts
|
||||
scripts: false
|
@ -0,0 +1,186 @@
|
||||
// require('babel-polyfill');
|
||||
|
||||
// Webpack config for development
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const assetsPath = path.resolve(__dirname, '../static/dist');
|
||||
const appconfig = require('../appconfig');
|
||||
const host = appconfig.hmrHost;
|
||||
const port = appconfig.hmrPort;
|
||||
|
||||
const autoprefixer = require('autoprefixer');
|
||||
|
||||
const WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
|
||||
const webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(
|
||||
require('./webpack-isomorphic-tools')
|
||||
);
|
||||
|
||||
// const { UnusedFilesWebpackPlugin } = require('unused-files-webpack-plugin');
|
||||
|
||||
const babelrc = fs.readFileSync('./.babelrc');
|
||||
let babelrcObject = {};
|
||||
|
||||
try {
|
||||
babelrcObject = JSON.parse(babelrc);
|
||||
} catch (err) {
|
||||
console.error('==> ERROR: Error parsing your .babelrc.');
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
const babelrcObjectDevelopment =
|
||||
(babelrcObject.env && babelrcObject.env.development) || {};
|
||||
const babelLoaderQuery = Object.assign(
|
||||
{},
|
||||
babelrcObject,
|
||||
babelrcObjectDevelopment
|
||||
);
|
||||
delete babelLoaderQuery.env;
|
||||
|
||||
babelLoaderQuery.plugins = babelLoaderQuery.plugins || [];
|
||||
if (babelLoaderQuery.plugins.indexOf('react-transform') < 0) {
|
||||
babelLoaderQuery.plugins.push('react-transform');
|
||||
}
|
||||
|
||||
babelLoaderQuery.extra = babelLoaderQuery.extra || {};
|
||||
if (!babelLoaderQuery.extra['react-transform']) {
|
||||
babelLoaderQuery.extra['react-transform'] = {};
|
||||
}
|
||||
if (!babelLoaderQuery.extra['react-transform'].transforms) {
|
||||
babelLoaderQuery.extra['react-transform'].transforms = [];
|
||||
}
|
||||
babelLoaderQuery.extra['react-transform'].transforms.push({
|
||||
transform: 'react-transform-hmr',
|
||||
imports: ['react'],
|
||||
locals: ['module'],
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
devtool: 'inline-source-map',
|
||||
context: path.resolve(__dirname, '..'),
|
||||
entry: {
|
||||
main: [
|
||||
'webpack-hot-middleware/client?path=http://' +
|
||||
host +
|
||||
':' +
|
||||
port +
|
||||
'/__webpack_hmr',
|
||||
'bootstrap-loader?extractStyles',
|
||||
'font-awesome-webpack!./src/theme/font-awesome.config.js',
|
||||
'./src/client.js',
|
||||
],
|
||||
},
|
||||
output: {
|
||||
path: assetsPath,
|
||||
filename: '[name]-[hash].js',
|
||||
chunkFilename: '[name]-[chunkhash].js',
|
||||
publicPath: 'http://' + host + ':' + port + appconfig.webpackPrefix,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.mjs$/,
|
||||
include: /node_modules/,
|
||||
type: 'javascript/auto',
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: 'babel-loader',
|
||||
},
|
||||
{
|
||||
test: /\.flow$/,
|
||||
loader: 'ignore-loader',
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]',
|
||||
'sass-loader?outputStyle=expanded&sourceMap',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: { limit: 10000, mimetype: 'application/font-woff' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: { limit: 10000, mimetype: 'application/font-woff' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: { limit: 10000, mimetype: 'application/octet-stream' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [{ loader: 'file-loader' }],
|
||||
},
|
||||
{
|
||||
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: { limit: 10000, mimetype: 'image/svg+xml' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: webpackIsomorphicToolsPlugin.regular_expression('images'),
|
||||
use: [{ loader: 'url-loader', options: { limit: 10240 } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
modules: ['src', 'node_modules'],
|
||||
extensions: ['.json', '.js', '.jsx', '.mjs'],
|
||||
},
|
||||
plugins: [
|
||||
// hot reload
|
||||
// new UnusedFilesWebpackPlugin({}),
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
jQuery: 'jquery',
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
postcss: [autoprefixer],
|
||||
}),
|
||||
new webpack.IgnorePlugin(/webpack-stats\.json$/),
|
||||
new webpack.DefinePlugin({
|
||||
__CLIENT__: true,
|
||||
__SERVER__: false,
|
||||
__DEVELOPMENT__: true,
|
||||
__DEVTOOLS__: true, // <-------- DISABLE redux-devtools HERE
|
||||
}),
|
||||
webpackIsomorphicToolsPlugin.development(),
|
||||
],
|
||||
};
|
@ -0,0 +1,192 @@
|
||||
// Webpack config for creating the production bundle.
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const CleanPlugin = require('clean-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const appconfig = require('../appconfig');
|
||||
|
||||
const relativeAssetsPath = '../static/dist';
|
||||
const assetsPath = path.join(__dirname, relativeAssetsPath);
|
||||
|
||||
const WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
|
||||
const webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(
|
||||
require('./webpack-isomorphic-tools')
|
||||
);
|
||||
|
||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
|
||||
const cleanOptions = {
|
||||
root: process.cwd(),
|
||||
verbose: true,
|
||||
dry: false,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
context: path.resolve(__dirname, '..'),
|
||||
entry: {
|
||||
main: ['bootstrap-loader?extractStyles', './src/client.js'],
|
||||
},
|
||||
output: {
|
||||
path: assetsPath,
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].js',
|
||||
publicPath: appconfig.webpackPrefix,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.mjs$/,
|
||||
include: /node_modules/,
|
||||
type: 'javascript/auto',
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: 'babel-loader',
|
||||
},
|
||||
{
|
||||
test: /\.flow$/,
|
||||
loader: 'ignore-loader',
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
'css-loader?modules&importLoaders=2&sourceMap=false',
|
||||
'sass-loader?outputStyle=expanded&sourceMap=false&sourceMapContents=false',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: { limit: 10000, mimetype: 'application/font-woff' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: { limit: 10000, mimetype: 'application/font-woff' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: { limit: 10000, mimetype: 'application/octet-stream' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [{ loader: 'file-loader' }],
|
||||
},
|
||||
{
|
||||
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: { limit: 10000, mimetype: 'image/svg+xml' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: webpackIsomorphicToolsPlugin.regular_expression('images'),
|
||||
use: [{ loader: 'url-loader', options: { limit: 10240 } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
modules: ['src', 'node_modules'],
|
||||
extensions: ['.json', '.js', '.jsx', '.mjs'],
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
runtimeChunk: {
|
||||
name: 'vendor',
|
||||
},
|
||||
minimizer: [
|
||||
new UglifyJSPlugin({
|
||||
uglifyOptions: {
|
||||
ecma: 8,
|
||||
warnings: false,
|
||||
compress: false,
|
||||
mangle: true,
|
||||
output: {
|
||||
comments: false,
|
||||
beautify: false,
|
||||
},
|
||||
toplevel: false,
|
||||
nameCache: null,
|
||||
ie8: false,
|
||||
keep_classnames: undefined,
|
||||
keep_fnames: false,
|
||||
safari10: false,
|
||||
},
|
||||
}),
|
||||
new OptimizeCssAssetsPlugin({
|
||||
assetNameRegExp: /\.css$/,
|
||||
}),
|
||||
],
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
commons: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: 'vendor',
|
||||
chunks: 'initial',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new CleanPlugin(['./static/dist/*.*'], cleanOptions),
|
||||
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
jQuery: 'jquery',
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: 'main.css',
|
||||
chunkFilename: 'main.css',
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
__CLIENT__: true,
|
||||
__SERVER__: false,
|
||||
__DEVELOPMENT__: false,
|
||||
__DEVTOOLS__: false,
|
||||
}),
|
||||
|
||||
// ignore dev config
|
||||
new webpack.IgnorePlugin(/\.\/dev/, /\/config$/),
|
||||
|
||||
// set global consts
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
// Useful to reduce the size of client-side libraries, e.g. react
|
||||
NODE_ENV: JSON.stringify('production'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
const Express = require('express');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const webpackConfig = require('./dev.config');
|
||||
const compiler = webpack(webpackConfig);
|
||||
const appconfig = require('../appconfig');
|
||||
|
||||
const host = appconfig.hmrHost;
|
||||
const port = appconfig.hmrPort;
|
||||
|
||||
const serverOptions = {
|
||||
contentBase: 'http://' + host + ':' + port,
|
||||
logLevel: 'silent',
|
||||
hot: true,
|
||||
inline: true,
|
||||
lazy: false,
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
headers: { 'Access-Control-Allow-Origin': '*' },
|
||||
stats: { colors: true },
|
||||
};
|
||||
|
||||
const app = new Express();
|
||||
|
||||
app.use(require('webpack-dev-middleware')(compiler, serverOptions));
|
||||
app.use(require('webpack-hot-middleware')(compiler));
|
||||
|
||||
app.listen(port, function onAppListening(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
console.info(
|
||||
'==> 🚧 Webpack development server listening on port %s',
|
||||
port
|
||||
);
|
||||
}
|
||||
});
|
@ -0,0 +1,101 @@
|
||||
var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
|
||||
|
||||
// see this link for more info on what all of this means
|
||||
// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
|
||||
module.exports = {
|
||||
// when adding "js" extension to asset types
|
||||
// and then enabling debug mode, it may cause a weird error:
|
||||
//
|
||||
// [0] npm run start-prod exited with code 1
|
||||
// Sending SIGTERM to other processes..
|
||||
//
|
||||
// debug: true,
|
||||
|
||||
assets: {
|
||||
images: {
|
||||
extensions: ['jpeg', 'jpg', 'png', 'gif'],
|
||||
parser: WebpackIsomorphicToolsPlugin.url_loader_parser,
|
||||
},
|
||||
fonts: {
|
||||
extensions: ['woff', 'woff2', 'ttf', 'eot'],
|
||||
parser: WebpackIsomorphicToolsPlugin.url_loader_parser,
|
||||
},
|
||||
svg: {
|
||||
extension: 'svg',
|
||||
parser: WebpackIsomorphicToolsPlugin.url_loader_parser,
|
||||
},
|
||||
// this whole "bootstrap" asset type is only used once in development mode.
|
||||
// the only place it's used is the Html.js file
|
||||
// where a <style/> tag is created with the contents of the
|
||||
// './src/theme/bootstrap.config.js' file.
|
||||
// (the aforementioned <style/> tag can reduce the white flash
|
||||
// when refreshing page in development mode)
|
||||
//
|
||||
// hooking into 'js' extension require()s isn't the best solution
|
||||
// and I'm leaving this comment here in case anyone finds a better idea.
|
||||
//bootstrap: {
|
||||
// extension: 'js',
|
||||
// include: ['./src/theme/bootstrap.config.js'],
|
||||
// filter: function(module, regex, options, log) {
|
||||
// function is_bootstrap_style(name) {
|
||||
// return name.indexOf('./src/theme/bootstrap.config.js') >= 0;
|
||||
// }
|
||||
// if (options.development) {
|
||||
// return is_bootstrap_style(module.name) && WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log);
|
||||
// }
|
||||
// // no need for it in production mode
|
||||
// },
|
||||
// // in development mode there's webpack "style-loader",
|
||||
// // so the module.name is not equal to module.name
|
||||
// path: WebpackIsomorphicToolsPlugin.style_loader_path_extractor,
|
||||
// parser: WebpackIsomorphicToolsPlugin.css_loader_parser
|
||||
//},
|
||||
|
||||
style_modules: {
|
||||
extensions: ['scss'],
|
||||
filter: (module, regex, options, log) => {
|
||||
if (options.development) {
|
||||
// in development mode there's webpack "style-loader",
|
||||
// so the module.name is not equal to module.name
|
||||
return WebpackIsomorphicToolsPlugin.style_loader_filter(
|
||||
module,
|
||||
regex,
|
||||
options,
|
||||
log
|
||||
);
|
||||
} else {
|
||||
// in production mode there's no webpack "style-loader",
|
||||
// so the module.name will be equal to the asset path
|
||||
return regex.test(module.name);
|
||||
}
|
||||
},
|
||||
path: (module, options, log) => {
|
||||
if (options.development) {
|
||||
// in development mode there's webpack "style-loader",
|
||||
// so the module.name is not equal to module.name
|
||||
return WebpackIsomorphicToolsPlugin.style_loader_path_extractor(
|
||||
module,
|
||||
options,
|
||||
log
|
||||
);
|
||||
} else {
|
||||
// in production mode there's no webpack "style-loader",
|
||||
// so the module.name will be equal to the asset path
|
||||
return module.name;
|
||||
}
|
||||
},
|
||||
parser: (module, options, log) => {
|
||||
if (options.development) {
|
||||
return WebpackIsomorphicToolsPlugin.css_modules_loader_parser(
|
||||
module,
|
||||
options,
|
||||
log
|
||||
);
|
||||
} else {
|
||||
// in production mode there's Extract Text Loader which extracts CSS text away
|
||||
return module.source;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
## Todo App Migrations
|
||||
|
||||
Update config.yaml to point to the right graphql-engine endpoint with/without admin_secret.
|
||||
|
||||
Run the following commands:
|
||||
```
|
||||
$ hasura migrate apply
|
||||
```
|
||||
This will apply the migrations and metadata.
|
@ -0,0 +1 @@
|
||||
endpoint: https://learn.hasura.io/graphql
|
@ -0,0 +1,167 @@
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 10.5 (Debian 10.5-1.pgdg90+1)
|
||||
-- Dumped by pg_dump version 10.1
|
||||
|
||||
-- Started on 2018-10-08 16:36:23 IST
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SET check_function_bodies = false;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
SET search_path = public, pg_catalog;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_with_oids = false;
|
||||
|
||||
--
|
||||
-- TOC entry 213 (class 1259 OID 24585)
|
||||
-- Name: users; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE users (
|
||||
id integer NOT NULL,
|
||||
name text,
|
||||
auth0_id text NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
last_seen timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 214 (class 1259 OID 24593)
|
||||
-- Name: online_users; Type: VIEW; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE VIEW online_users AS
|
||||
SELECT users.name,
|
||||
users.last_seen
|
||||
FROM users
|
||||
WHERE (users.last_seen >= (now() - '00:00:30'::interval));
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 212 (class 1259 OID 24576)
|
||||
-- Name: todos; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE todos (
|
||||
id integer NOT NULL,
|
||||
text text NOT NULL,
|
||||
is_completed boolean DEFAULT false NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone,
|
||||
is_public boolean DEFAULT false NOT NULL,
|
||||
user_id text NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 215 (class 1259 OID 24597)
|
||||
-- Name: todos_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE todos_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2981 (class 0 OID 0)
|
||||
-- Dependencies: 215
|
||||
-- Name: todos_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE todos_id_seq OWNED BY todos.id;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 216 (class 1259 OID 24599)
|
||||
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE users_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2982 (class 0 OID 0)
|
||||
-- Dependencies: 216
|
||||
-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE users_id_seq OWNED BY users.id;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2839 (class 2604 OID 24612)
|
||||
-- Name: todos id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY todos ALTER COLUMN id SET DEFAULT nextval('todos_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2842 (class 2604 OID 24613)
|
||||
-- Name: users id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY users ALTER COLUMN id SET DEFAULT nextval('users_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2844 (class 2606 OID 24604)
|
||||
-- Name: todos todos_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY todos
|
||||
ADD CONSTRAINT todos_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2846 (class 2606 OID 24606)
|
||||
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (auth0_id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2847 (class 2606 OID 24607)
|
||||
-- Name: todos todos_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY todos
|
||||
ADD CONSTRAINT todos_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(auth0_id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2980 (class 0 OID 0)
|
||||
-- Dependencies: 4
|
||||
-- Name: public; Type: ACL; Schema: -; Owner: -
|
||||
--
|
||||
|
||||
-- Completed on 2018-10-08 16:36:25 IST
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: alter table "public"."todos" rename column "title" to "text";
|
||||
type: run_sql
|
@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: alter table "public"."todos" rename column "text" to "title";
|
||||
type: run_sql
|
@ -0,0 +1 @@
|
||||
[]
|
@ -0,0 +1,4 @@
|
||||
- args:
|
||||
cascade: true
|
||||
sql: drop table users cascade;
|
||||
type: run_sql
|
@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: DROP TABLE "public"."users"
|
||||
type: run_sql
|
@ -0,0 +1,9 @@
|
||||
- args:
|
||||
sql: CREATE TABLE "public"."users"("id" text NOT NULL, "name" text NOT NULL, "created_at"
|
||||
timestamptz NOT NULL DEFAULT now(), "last_seen" timestamptz NOT NULL, PRIMARY
|
||||
KEY ("id") );
|
||||
type: run_sql
|
||||
- args:
|
||||
name: users
|
||||
schema: public
|
||||
type: add_existing_table_or_view
|
@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: ALTER TABLE "public"."users" ALTER COLUMN "last_seen" SET NOT NULL;
|
||||
type: run_sql
|
@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: ALTER TABLE "public"."users" ALTER COLUMN "last_seen" DROP NOT NULL;
|
||||
type: run_sql
|
@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: alter table "public"."todos" drop constraint todos_user_id_fkey;
|
||||
type: run_sql
|
@ -0,0 +1,4 @@
|
||||
- args:
|
||||
sql: alter table "public"."todos" add constraint todos_user_id_fkey foreign key
|
||||
("user_id") references "public"."users"("id") on update cascade on delete cascade;
|
||||
type: run_sql
|
@ -0,0 +1,6 @@
|
||||
- args:
|
||||
relationship: user
|
||||
table:
|
||||
name: todos
|
||||
schema: public
|
||||
type: drop_relationship
|
@ -0,0 +1,8 @@
|
||||
- args:
|
||||
name: user
|
||||
table:
|
||||
name: todos
|
||||
schema: public
|
||||
using:
|
||||
foreign_key_constraint_on: user_id
|
||||
type: create_object_relationship
|
@ -0,0 +1,6 @@
|
||||
- args:
|
||||
relationship: todos
|
||||
table:
|
||||
name: users
|
||||
schema: public
|
||||
type: drop_relationship
|