add online graphiql to community tools (#716)

This commit is contained in:
Praveen Durairaj 2018-10-25 14:57:02 +05:30 committed by Shahidh K Muhammed
parent 810b440089
commit 1266bbf354
73 changed files with 7617 additions and 0 deletions

View File

@ -0,0 +1,4 @@
{
"presets": ["stage-0", "env", "react"],
"plugins": ["transform-class-properties", "transform-react-remove-prop-types", "istanbul"]
}

View File

@ -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

View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,2 @@
webpack/*
src/utils.js

View File

@ -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
}
}

View 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/*

View File

@ -0,0 +1,5 @@
*
**/*
!static/dist/main.js
!static/dist/vendor.js
!static/dist/main.css

View File

@ -0,0 +1,22 @@
FROM node:8.9-alpine
# Create app directory
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
RUN rm -Rf node_modules
EXPOSE 8080
# serve dist folder on port 8080
CMD ["serve", "-s", "static", "-p", "8080"]

View File

@ -0,0 +1,33 @@
## GraphiQL Demo
This version of GraphiQL is a fork of the original version with a simple header management UI and a fix for subscription.
## 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 } }'
```
**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.
For a quick Docker based deployment, use `docker build -t graphiql .` && `docker run -d -p 8080:8080 graphiql` for running the production build locally.
You can also use now.sh for a cloud deployment. Just simply run `now` to deploy this and get a live URL.

View File

@ -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',
};

View File

@ -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);

View File

@ -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');

View File

@ -0,0 +1,4 @@
{
"name": "graphqurl-graphiql",
"alias": "hasura-graphiql.now.sh"
}

View File

@ -0,0 +1,142 @@
{
"name": "graphqurl-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",
"deploy": "now --docker --public && now alias && now alias hasura-graphiql.now.sh graphiql-online.com"
},
"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",
"babel-polyfill": "^6.26.0",
"deep-equal": "^1.0.1",
"graphiql": "^0.11.11",
"graphql": "^0.13.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"
}
}

View File

@ -0,0 +1,93 @@
/**
* 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: '/' })}
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
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -0,0 +1,240 @@
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 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,
updateGraphQLEndpoint,
};

View File

@ -0,0 +1,52 @@
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() {
const localStorageUrl = window.localStorage.getItem('ONLINE_GRAPHIQL_ENDPOINT');
if (!this.props.graphqlEndpoint && localStorageUrl === null) {
this.props.dispatch(push('/'));
}
}
render() {
const 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}
graphqlEndpoint={localStorageUrl}
/>
);
return (
<div className={'container-fluid ' + styles.padd_remove}>
<Helmet title="GraphiQL" />
<div className={wrapperClass}>{requestWrapper}</div>
</div>
);
}
}
ApiExplorer.propTypes = {
modalState: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
route: PropTypes.object.isRequired,
headerFocus: PropTypes.bool.isRequired,
};
export default ApiExplorer;

View File

@ -0,0 +1,808 @@
@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;
}
}
.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;
padding-left: 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;
}
}
}

View File

@ -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;

View File

@ -0,0 +1,325 @@
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-11 ' + 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={'col-xs-1 ' + styles.padd_remove}>
<button onClick={this.changeEndpoint.bind(this)} className={styles.changeEndpoint + ' btn btn-sm btn-small btn-info'}>
Change Endpoint
</button>
</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-Access-Key' &&
!this.state.accessKeyVisible
? 'password'
: 'text'
}
/>
</td>
{header.isNewHeader ? null : (
<td>
{header.key === 'X-Hasura-Access-Key' ? (
<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}
/>
);
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,
};
export default ApiRequest;

View File

@ -0,0 +1,28 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class ApiRequestDetails extends Component {
render() {
const styles = require('./ApiExplorer.scss');
return (
<div className={styles.apiRequestWrapper + ' ' + styles.apiContentPadd}>
<div className={styles.apiRequestContent}>{this.props.description}</div>
<div className={styles.built}>
Built with <i className="fa fa-heart" /> by <a href={'http://hasura.io/'} target={'_blank'}>Hasura</a>
</div>
<div className={styles.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>
);
}
}
ApiRequestDetails.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
};
export default ApiRequestDetails;

View File

@ -0,0 +1,63 @@
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}
/>
<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}
/>
</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,
};
export default ApiRequestWrapper;

View File

@ -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

View File

@ -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

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,65 @@
import React, { Component } from 'react';
import GraphiQL from 'hasura-console-graphiql';
import PropTypes from 'prop-types';
import ErrorBoundary from './ErrorBoundary';
import { graphQLFetcherFinal } from './Actions';
import './GraphiQL.css';
class GraphiQLWrapper extends Component {
constructor(props) {
super(props);
this.state = {
schema: null,
error: false,
onBoardingEnabled: false,
};
}
shouldComponentUpdate(nextProps) {
return !nextProps.headerFocus;
}
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);
}
}
return (
<ErrorBoundary>
<div
className={
'react-container-graphql ' +
styles.wd100 +
' ' +
styles.graphQLHeight
}
>
<GraphiQL {...graphiqlProps} />
</div>
</ErrorBoundary>
);
}
}
GraphiQLWrapper.propTypes = {
dispatch: PropTypes.func.isRequired,
data: PropTypes.object.isRequired,
headerFocus: PropTypes.bool.isRequired,
};
export default GraphiQLWrapper;

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,82 @@
const existingHeaders = window.__env.headers;
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,
})
});
}
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 };

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

View File

@ -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 };

View File

@ -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,
};

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -0,0 +1,9 @@
@import "../Common.scss";
.breadcrumbWrapper {
padding: 20px 0px;
padding-bottom: 0px;
font-size: 16px;
font-weight: 400;
color: #767E93;
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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);
}
}

View File

@ -0,0 +1,57 @@
@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;
padding-left: 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;
}

View File

@ -0,0 +1,56 @@
import React from 'react';
import PropTypes from 'prop-types';
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 className="loginWrapper">
<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>
);
}
}
LoginComponent.propTypes = {
dispatch: PropTypes.func.isRequired,
};
export default LoginComponent;

View File

@ -0,0 +1,12 @@
import LoginComponent from './Login';
const generatedLoginComponent = connect => {
const mapStateToProps = state => {
return {
...state.apiexplorer,
};
};
return connect(mapStateToProps)(LoginComponent);
};
export default generatedLoginComponent;

View File

@ -0,0 +1,6 @@
/**
* Export component modules
*/
export App from './App/App';
export PageNotFound from './404/PageNotFound';

View File

@ -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
);

View File

@ -0,0 +1,102 @@
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" />
{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}'
};`,
}}
/>
</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>
);
}
}

View File

@ -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);
};

View File

@ -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);
}

View File

@ -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;

View File

@ -0,0 +1,22 @@
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 generatedLoginComponent from './components/Login/LoginGenerator';
const routes = () => {
// loads schema
return (
<Route path="/" component={App}>
<Route path="">
<IndexRoute component={generatedLoginComponent(connect)} />
</Route>
<Route path="/graphiql" component={generatedApiExplorer(connect)} />
<Route path="404" component={PageNotFound} status="404" />
<Route path="*" component={PageNotFound} status="404" />
</Route>
);
};
export default routes;

View 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'
);
}

View 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,
},
};

View File

@ -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;

View 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;
}
}
}
}
}
}

View File

@ -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,
},
};

View File

@ -0,0 +1,7 @@
/**
* Configuration file for font-awesome-webpack
*
*/
// Example:
// @fa-border-color: #ddd;

View File

@ -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;

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,53 @@
<html lang="en-us">
<head>
<link rel="icon" type="image/png" href="./favicon.png" />
<script>
window.__env = {
graphqlEndpoint: '',
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="/dist/main.css" charset="UTF-8"/>
<script src="/dist/vendor.js" charset="UTF-8"></script>
<script src="/dist/main.js" charset="UTF-8"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -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

View File

@ -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(),
],
};

View File

@ -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'),
},
}),
],
};

View File

@ -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
);
}
});

View File

@ -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;
}
},
},
},
};