├── .babelrc ├── .bablerc ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── _protographql_tests_ ├── buildENV.test.js ├── buildGQLInputTypes.test.js └── buildGQLSchema.test.js ├── apollo-server ├── .env ├── .gitignore ├── db │ ├── createTables.sql │ └── sqlPool.js ├── graphql │ ├── resolvers.js │ └── schema.js ├── package.json ├── public │ ├── index.html │ ├── logo.png │ └── stylesheet.css ├── server.js └── tests │ └── tests.js ├── index.html ├── main.js ├── package.json ├── palette.css ├── public ├── assets │ └── pictures │ │ ├── Code_Screenshot.png │ │ ├── Export_Screenshot.png │ │ ├── GitHub-Mark-Light-64px.png │ │ ├── GraphQL-Logo.png │ │ ├── GraphQL_Logo.png │ │ ├── ProtoGraphQLLogo.png │ │ ├── ProtoGraphQLLogo64.png │ │ ├── ProtographQLBanner.png │ │ ├── Rest-Logo.png │ │ ├── Schema_Screenshot.png │ │ ├── add-table demo.mov.gif │ │ ├── icon.png │ │ ├── icon │ │ ├── icon.icns │ │ ├── icon.ico │ │ └── icon.png │ │ ├── tree visializer demo.gif │ │ ├── v2-add-tables.gif │ │ ├── v2-codeview.png │ │ ├── v2-export.png │ │ ├── v2-import-tables.gif │ │ ├── v2-schema-view.png │ │ ├── v2-test-queries.gif │ │ └── v2-visualizer.gif ├── index.html └── styles.css ├── src ├── App.jsx ├── actions │ └── actionTypes.jsx ├── components │ ├── header │ │ └── header.jsx │ ├── popup │ │ ├── exportPopUp.jsx │ │ ├── instructions.jsx │ │ ├── tableField.jsx │ │ ├── tableForm.jsx │ │ ├── tableInput.jsx │ │ ├── tableNameInput.jsx │ │ └── welcome.jsx │ ├── sideBar │ │ ├── navButton.jsx │ │ ├── navSidebar.jsx │ │ ├── noTableButton.jsx │ │ ├── visualizerSidebar.jsx │ │ └── vizType.jsx │ └── view │ │ ├── codeView.jsx │ │ ├── mainView.jsx │ │ ├── schemaTable.jsx │ │ ├── schemaView.jsx │ │ ├── testsView.jsx │ │ └── visualizeView.jsx ├── container │ └── mainContainer.jsx ├── index.jsx ├── pg-import │ ├── pgQuery.js │ └── sqlPool.js ├── state │ ├── initialState.jsx │ ├── mockState.jsx │ └── store.jsx └── utils │ ├── buildENV.js │ ├── buildExportTestSuite.js │ ├── buildGQLInputTypes.js │ ├── buildGQLMutationTypes.js │ ├── buildGQLObjTypes.js │ ├── buildGQLQueryType.js │ ├── buildGQLResolvers.js │ ├── buildGQLSchema.js │ ├── buildSQLScripts.js │ ├── buildVisualizerJson.js │ ├── deepClone.js │ └── tabs.js ├── tests └── tests.js ├── tsconfig.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | 5 | //hello 6 | -------------------------------------------------------------------------------- /.bablerc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [[ 3 | "@babel/preset-env", { 4 | "useBuiltIns": "entry" 5 | }], 6 | "@babel/preset-react"], 7 | "plugins": [ 8 | "@babel/plugin-proposal-class-properties", 9 | "@babel/plugin-proposal-export-default-from", 10 | "react-hot-loader/babel" 11 | ] 12 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "globals": { 8 | "Atomics": "readonly", 9 | "SharedArrayBuffer": "readonly" 10 | }, 11 | "parserOptions": { 12 | "ecmaFeatures": { 13 | "jsx": true 14 | }, 15 | "ecmaVersion": 2018, 16 | "sourceType": "module" 17 | }, 18 | "plugins": [ 19 | "react" 20 | ], 21 | "rules": { 22 | } 23 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | Release.key 4 | winehq.key 5 | .env 6 | out 7 | public/bundle.js 8 | public/bundle.js.map 9 | dist 10 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 protographql 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # ProtoGraphQL ver. 2.0 4 | 5 | *** The latest release of ProtoGraphQL includes a new view that can jumpstart your query testing with the Jest framework and the ability to import tables from an existing database. *** 6 | 7 | ProtoGraphQL is a **prototyping tool** that empowers developers to build and visualize GraphQL schemas and queries without writing any code. Once users import or input their relational database tables, ProtoGraphQL can generate an export package that allows developers to spin up a customized and functional GraphQL Apollo Server as an independent app. (As of version 2.0 custom mutations are also supported!) 8 | 9 | ProtoGraphQL is in beta. Please post any issues to our GitHub - we are actively looking for opportunities to improve this tool and we welcome your feedback. 10 | 11 | Upcoming releases will improve the “Schema” view and add support for NoSQL databases. 12 | 13 | ## Getting Started: 14 | 15 | 1. Download: [Mac](https://github.com/oslabs-beta/protographql/releases/download/v1.0.0-beta/ProtoGraphQL-1.0.0.dmg), [Windows](https://github.com/oslabs-beta/protographql/releases/download/v1.0.0-beta/ProtoGraphQL.Setup.1.0.0.exe), [Linux](https://github.com/oslabs-beta/protographql/releases/download/v1.0.0-beta/protographql_1.0.0_amd64.deb) 16 | 17 | 2. Extract file 18 | 19 | 3. Run application 20 | 21 | ## How to Use: 22 | 23 | 1. When the application starts you will be given the option to either **CREATE YOUR TABLES** or **IMPORT TABLES**. To create your tables, click the “Add Table” button in the lower left corner of the screen. When your config is complete, click **Save**. When importing, simply paste your database URI in the field and click the **Connect** button. In either case, your tables will be displayed in the main view when you are done. 24 | 25 |

26 | 27 | 2. Navigate to the alternate views within the app using the tabs on the left: **Schema**, **Code**, **Visualize**, and **Tests** 28 | 29 | * **Schema** - view, edut or delete tables you've added. 30 | 31 |

32 | 33 | * **Code** - view generated GraphQL and SQL code. 34 | 35 |

36 | 37 | * **Visualize** - view the GraphQL schema as a simple tree. 38 | 39 |

40 | 41 | * **Tests** - create and export query and response pairs from a custom GraphQL Endpoint. 42 | 43 |

44 | 45 | 3. Export your code by clicking the **Export** icon in the upper right. 46 | 47 |

48 | 49 | 4. Enter your Postgres database URI, then select the directory you want to save your compressed GraphQL server package in. 50 | 51 | 52 | ## How to Run GraphQL Server: 53 | 54 | There are several libraries we could have used to create a GraphQL server, but we decided to use Apollo Server – the most popular library to setup an endpoint for responding to incoming GraphQL requests in JavaScript. 55 | 56 | 1. Extract apollo-server.zip file 57 | 58 | 2. Open the project 59 | 60 | 3. Install dependencies 61 | ``` 62 | npm install 63 | ``` 64 | 65 | 4. Run the server and point your browser to localhost:3000 66 | ``` 67 | npm start 68 | ``` 69 | 70 | 5. Use Apollo Server Playground to mock client GraphQL queries and responses to your server. [Learn more about constructing GraphQL Queries here](https://graphql.org/learn/queries/) 71 | 72 | ## Contributors: 73 | 74 | 75 | 76 | - Alena Budzko | [@AlenaBudzko](https://github.com/AlenaBudzko) 77 | - Bryan Fong | [@bryanfong-dev](https://github.com/bryanfong-dev) 78 | - Rodolfo Guzman | [@Rodolfoguzman25](https://github.com/Rodolfoguzman25) 79 | - Haris Hambasic | [@hambasicharis1995](https://github.com/hambasicharis1995) 80 | - Jarred Jack Harewood | [@jackhajb](https://github.com/jackhajb) 81 | - Geoffrey Lin | [@geofflin](https://github.com/geofflin) 82 | - Michele Moody | [@Milmoody](https://github.com/Milmoody) 83 | - Jessica Vaughan | [@jessicavaughan820](https://github.com/jessicavaughan820) 84 | - Vance Wallace | [@Vancito](https://github.com/Vancito) 85 | -------------------------------------------------------------------------------- /_protographql_tests_/buildENV.test.js: -------------------------------------------------------------------------------- 1 | import buildEnvURI from '../src/utils/buildENV'; 2 | import buildENV from '../src/utils/buildENV'; 3 | 4 | test('When trying to create the Environment URI the result is not null: ', () => { 5 | expect(buildEnvURI("thisIsNotNull")).not.toBeNull; 6 | }); 7 | 8 | test('When trying to create the Environment URI the result is a string: ', () => { 9 | expect(typeof((buildENV("thisIsAString")))).toBe('string'); 10 | }); 11 | 12 | test('Create the correct Environment URI given test input: ', () => { 13 | expect(buildEnvURI("theCorrectURIString")).toBe("DB_URI=theCorrectURIString"); 14 | }); 15 | -------------------------------------------------------------------------------- /_protographql_tests_/buildGQLInputTypes.test.js: -------------------------------------------------------------------------------- 1 | import buildGQLInputTypes from '../src/utils/buildGQLInputTypes'; 2 | import { build } from 'protobufjs'; 3 | 4 | const testInput = { 5 | 0: { 6 | type: 'FirstTable', 7 | fields: { 8 | 0: { 9 | name: 'FirstColumn', 10 | type: 'ID', 11 | primaryKey: true, 12 | unique: true, 13 | required: false, 14 | defaultValue: '1', 15 | relationSelected: false, 16 | relation: { 17 | tableIndex: -1, 18 | fieldIndex: -1, 19 | refType: '' 20 | }, 21 | tableNum: 0, 22 | fieldNum: 0, 23 | queryable: false 24 | }, 25 | 1: { 26 | name: 'SecondColumn', 27 | type: 'String', 28 | primaryKey: false, 29 | unique: false, 30 | required: true, 31 | defaultValue: 'thisIsADefaultValue', 32 | relationSelected: false, 33 | relation: { 34 | tableIndex: -1, 35 | fieldIndex: -1, 36 | refType: '' 37 | }, 38 | tableNum: 0, 39 | fieldNum: 1, 40 | queryable: true 41 | }, 42 | 2: { 43 | name: 'ThirdColumn', 44 | type: 'String', 45 | primaryKey: false, 46 | unique: false, 47 | required: true, 48 | defaultValue: '', 49 | relationSelected: false, 50 | relation: { 51 | tableIndex: -1, 52 | fieldIndex: -1, 53 | refType: '' 54 | }, 55 | tableNum: 0, 56 | fieldNum: 2, 57 | queryable: false 58 | } 59 | }, 60 | fieldIndex: 3, 61 | tableID: 0 62 | }, 63 | 1: { 64 | type: 'SecondTable', 65 | fields: { 66 | 0: { 67 | name: 'SecondTableFirstColumn', 68 | type: 'ID', 69 | primaryKey: true, 70 | unique: true, 71 | required: false, 72 | defaultValue: '', 73 | relationSelected: false, 74 | relation: { 75 | tableIndex: -1, 76 | fieldIndex: -1, 77 | refType: '' 78 | }, 79 | tableNum: 1, 80 | fieldNum: 0, 81 | queryable: true 82 | }, 83 | 1: { 84 | name: 'SecondSecondColumn', 85 | type: 'String', 86 | primaryKey: false, 87 | unique: false, 88 | required: true, 89 | defaultValue: 'anotherDefaultValue', 90 | relationSelected: false, 91 | relation: { 92 | tableIndex: -1, 93 | fieldIndex: -1, 94 | refType: '' 95 | }, 96 | tableNum: 1, 97 | fieldNum: 1, 98 | queryable: true 99 | }, 100 | 2: { 101 | name: 'SecondThirdColumn', 102 | type: 'ID', 103 | primaryKey: false, 104 | unique: false, 105 | required: true, 106 | defaultValue: '', 107 | relationSelected: true, 108 | relation: { 109 | tableIndex: '0', 110 | fieldIndex: '0', 111 | refType: 'many to one' 112 | }, 113 | tableNum: 1, 114 | fieldNum: 2, 115 | queryable: true 116 | } 117 | }, 118 | fieldIndex: 3, 119 | tableID: 1 120 | }, 121 | }; 122 | 123 | const testOutput = ` input FirstTableInput { 124 | FirstColumn: ID, 125 | SecondColumn: String!, 126 | ThirdColumn: String!, 127 | } 128 | 129 | input SecondTableInput { 130 | SecondTableFirstColumn: ID, 131 | SecondSecondColumn: String!, 132 | firsttable: FirstTableInput, 133 | } 134 | 135 | ` 136 | 137 | test('Building GQL Input Types does not return null: ', () => { 138 | expect(buildGQLInputTypes(testInput)).not.toBeNull; 139 | }); 140 | 141 | test('When trying to create the GQL Input Types the result is a string: ', () => { 142 | expect(typeof(buildGQLInputTypes(testInput))).toBe('string'); 143 | }); 144 | 145 | test('Building GQL Input Types works given test input: ', () => { 146 | expect(buildGQLInputTypes(testInput)).toBe(testOutput); 147 | }); -------------------------------------------------------------------------------- /_protographql_tests_/buildGQLSchema.test.js: -------------------------------------------------------------------------------- 1 | import buildGQLSchema from '../src/utils/buildGQLSchema'; 2 | import { build } from 'protobufjs'; 3 | 4 | const testInput = { 5 | 0: { 6 | type: 'FirstTable', 7 | fields: { 8 | 0: { 9 | name: 'FirstColumn', 10 | type: 'ID', 11 | primaryKey: true, 12 | unique: true, 13 | required: false, 14 | defaultValue: '1', 15 | relationSelected: false, 16 | relation: { 17 | tableIndex: -1, 18 | fieldIndex: -1, 19 | refType: '' 20 | }, 21 | tableNum: 0, 22 | fieldNum: 0, 23 | queryable: false 24 | }, 25 | 1: { 26 | name: 'SecondColumn', 27 | type: 'String', 28 | primaryKey: false, 29 | unique: false, 30 | required: true, 31 | defaultValue: 'thisIsADefaultValue', 32 | relationSelected: false, 33 | relation: { 34 | tableIndex: -1, 35 | fieldIndex: -1, 36 | refType: '' 37 | }, 38 | tableNum: 0, 39 | fieldNum: 1, 40 | queryable: true 41 | }, 42 | 2: { 43 | name: 'ThirdColumn', 44 | type: 'String', 45 | primaryKey: false, 46 | unique: false, 47 | required: true, 48 | defaultValue: '', 49 | relationSelected: false, 50 | relation: { 51 | tableIndex: -1, 52 | fieldIndex: -1, 53 | refType: '' 54 | }, 55 | tableNum: 0, 56 | fieldNum: 2, 57 | queryable: false 58 | } 59 | }, 60 | fieldIndex: 3, 61 | tableID: 0 62 | }, 63 | 1: { 64 | type: 'SecondTable', 65 | fields: { 66 | 0: { 67 | name: 'SecondTableFirstColumn', 68 | type: 'ID', 69 | primaryKey: true, 70 | unique: true, 71 | required: false, 72 | defaultValue: '', 73 | relationSelected: false, 74 | relation: { 75 | tableIndex: -1, 76 | fieldIndex: -1, 77 | refType: '' 78 | }, 79 | tableNum: 1, 80 | fieldNum: 0, 81 | queryable: true 82 | }, 83 | 1: { 84 | name: 'SecondSecondColumn', 85 | type: 'String', 86 | primaryKey: false, 87 | unique: false, 88 | required: true, 89 | defaultValue: 'anotherDefaultValue', 90 | relationSelected: false, 91 | relation: { 92 | tableIndex: -1, 93 | fieldIndex: -1, 94 | refType: '' 95 | }, 96 | tableNum: 1, 97 | fieldNum: 1, 98 | queryable: true 99 | }, 100 | 2: { 101 | name: 'SecondThirdColumn', 102 | type: 'ID', 103 | primaryKey: false, 104 | unique: false, 105 | required: true, 106 | defaultValue: '', 107 | relationSelected: true, 108 | relation: { 109 | tableIndex: '0', 110 | fieldIndex: '0', 111 | refType: 'many to one' 112 | }, 113 | tableNum: 1, 114 | fieldNum: 2, 115 | queryable: true 116 | } 117 | }, 118 | fieldIndex: 3, 119 | tableID: 1 120 | }, 121 | }; 122 | 123 | const testOutput = `const { gql } = require('apollo-server-express'); 124 | 125 | const typeDefs = gql\` 126 | 127 | type FirstTable { 128 | FirstColumn: ID 129 | SecondColumn: String! 130 | ThirdColumn: String! 131 | } 132 | 133 | type SecondTable { 134 | SecondTableFirstColumn: ID 135 | SecondSecondColumn: String! 136 | firsttable: FirstTable 137 | } 138 | 139 | input FirstTableInput { 140 | FirstColumn: ID, 141 | SecondColumn: String!, 142 | ThirdColumn: String!, 143 | } 144 | 145 | input SecondTableInput { 146 | SecondTableFirstColumn: ID, 147 | SecondSecondColumn: String!, 148 | firsttable: FirstTableInput, 149 | } 150 | 151 | type Mutation { 152 | addFirstTable( 153 | input: FirstTableInput 154 | ): [FirstTable] 155 | addSecondTable( 156 | input: SecondTableInput 157 | ): [SecondTable] 158 | } 159 | 160 | type Query { 161 | getAllFirstTable: [FirstTable] 162 | getFirstTable( 163 | SecondColumn: String 164 | ): [FirstTable] 165 | getAllSecondTable: [SecondTable] 166 | getSecondTable( 167 | SecondTableFirstColumn: ID, 168 | SecondSecondColumn: String, 169 | SecondThirdColumn: ID 170 | ): [SecondTable] 171 | } 172 | 173 | \`; 174 | 175 | module.exports = typeDefs; 176 | ` 177 | 178 | test('Building GQL Schema does not return null: ', () => { 179 | expect(buildGQLSchema(testInput)).not.toBeNull; 180 | }); 181 | 182 | test('When trying to create the GQL Schema the result is a string: ', () => { 183 | expect(typeof(buildGQLSchema(testInput))).toBe('string'); 184 | }); 185 | 186 | test('Building GQL Schema works given test input: ', () => { 187 | expect(buildGQLSchema(testInput)).toBe(testOutput); 188 | }); -------------------------------------------------------------------------------- /apollo-server/.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/apollo-server/.env -------------------------------------------------------------------------------- /apollo-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .DS_Store 4 | .env -------------------------------------------------------------------------------- /apollo-server/db/createTables.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/apollo-server/db/createTables.sql -------------------------------------------------------------------------------- /apollo-server/db/sqlPool.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | require("dotenv").config(); 3 | 4 | const URI = process.env.DB_URI 5 | 6 | 7 | const pool = new Pool({ 8 | connectionString: URI, 9 | ssl: true, 10 | }) 11 | 12 | pool.connect((err, client, done) => { 13 | if (err) return console.log(`Error connecting to db, ${err}`); 14 | console.log('Connected to db 😄') 15 | done(); 16 | }) 17 | 18 | module.exports = pool; 19 | -------------------------------------------------------------------------------- /apollo-server/graphql/resolvers.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/apollo-server/graphql/resolvers.js -------------------------------------------------------------------------------- /apollo-server/graphql/schema.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/apollo-server/graphql/schema.js -------------------------------------------------------------------------------- /apollo-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apolloGql", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon server.js --open" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "apollo-server-express": "^2.6.2", 15 | "dotenv": "^8.0.0", 16 | "express": "^4.17.1", 17 | "graphql": "^14.3.1", 18 | "pg": "^7.11.0" 19 | }, 20 | "devDependencies": { 21 | "nodemon": "^1.19.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apollo-server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ProtographQL 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 |

⭐ Thank you for using our open source project! ⭐

17 |

Please support us by starring us on 18 | 19 | Github 20 | 21 |

22 | 23 | 25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /apollo-server/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/apollo-server/public/logo.png -------------------------------------------------------------------------------- /apollo-server/public/stylesheet.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color:#ccc; 3 | color: white; 4 | font-family: "Roboto", sans-serif; 5 | display:flex; 6 | flex-direction: column; 7 | align-items: center; 8 | justify-content: center; 9 | height: 90vh; 10 | min-width: 1200px; 11 | min-height: 700px; 12 | } 13 | 14 | #main { 15 | background:#324353; 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | height: 80vh; 20 | width: 90vw; 21 | border-radius: 5px; 22 | box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.10); 23 | max-width: 1150px; 24 | } 25 | 26 | #logo-div{ 27 | height: auto; 28 | margin-right: 10%; 29 | display: flex; 30 | flex-direction: column; 31 | align-items: center; 32 | justify-content: center; 33 | } 34 | 35 | #logo{ 36 | height: 250px; 37 | margin-bottom: 20px; 38 | animation-delay: 2.5s; 39 | animation-name: spin; 40 | animation-duration: 100000ms; 41 | animation-iteration-count: infinite; 42 | animation-timing-function: linear; 43 | } 44 | 45 | #logo:hover{ 46 | animation-duration: 100ms; 47 | animation-timing-function: ease-in; 48 | cursor: pointer; 49 | } 50 | 51 | #text-div{ 52 | display:flex; 53 | flex-direction: column; 54 | align-items: center; 55 | font-size: 15px; 56 | text-align: center; 57 | } 58 | 59 | h1, h2{ 60 | font-size: 1.5em; 61 | font-weight: 500; 62 | margin: 5px; 63 | letter-spacing: .2px; 64 | } 65 | 66 | span{ 67 | font-weight: 600; 68 | cursor: pointer; 69 | text-decoration: underline; 70 | transition: all .15s ease-in-out; 71 | } 72 | 73 | a { 74 | color: white; 75 | } 76 | 77 | a:hover{ 78 | color: #DD399C; 79 | } 80 | 81 | p { 82 | font-size: .80em; 83 | color: #222; 84 | margin-top: 0; 85 | } 86 | 87 | button { 88 | margin-top: 25px; 89 | padding: 15px 20px; 90 | font-size: 1em; 91 | border-radius: 3px; 92 | cursor: pointer; 93 | letter-spacing: 1px; 94 | background-color: white; 95 | border: 1px solid white; 96 | color: #222; 97 | transition: all .2s ease-in-out; 98 | box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.10); 99 | } 100 | 101 | button:hover{ 102 | transform: scale(1.05); 103 | color: #DD399C; 104 | } 105 | 106 | @keyframes spin { 107 | from { 108 | transform:rotate(0deg); 109 | } 110 | to { 111 | transform:rotate(360deg); 112 | } 113 | } -------------------------------------------------------------------------------- /apollo-server/server.js: -------------------------------------------------------------------------------- 1 | const { ApolloServer } = require('apollo-server-express'); 2 | const typeDefs = require('./graphql/schema'); 3 | const resolvers = require('./graphql/resolvers'); 4 | 5 | const express = require('express'); 6 | const app = express(); 7 | const path = require('path'); 8 | const server = new ApolloServer({ typeDefs, resolvers }); 9 | 10 | server.applyMiddleware({ app }); 11 | 12 | app.use(express.static(path.join(__dirname, './public'))) 13 | 14 | app.listen({ port: 3000 }, () => { 15 | console.log('ProtoGraphQL is ready for use at http://localhost:3000 🚀') 16 | }); -------------------------------------------------------------------------------- /apollo-server/tests/tests.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/apollo-server/tests/tests.js -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ProtoGraphQL 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, dialog, Menu, shell } = require('electron'); 2 | const path = require('path'); 3 | const ipcMain = require('electron').ipcMain; 4 | const archiver = require('archiver'); 5 | const fs = require('fs'); 6 | const buildExportTestSuite = require('./src/utils/buildExportTestSuite.js'); 7 | const pgQuery = require('./src/pg-import/pgQuery.js') 8 | 9 | // Global reference of the window object to avoid JS garbage collection 10 | // when window is created 11 | let win; 12 | 13 | function createWindow() { 14 | win = new BrowserWindow({ 15 | width: 800, 16 | height: 600, 17 | minHeight: 600, 18 | webPreferences: { 19 | nodeIntegration: true 20 | }, 21 | // this is only for Windows and Linux 22 | icon: path.join(__dirname, 'public/assets/pictures/ProtoGraphQLLogo64.png') 23 | }); 24 | 25 | win.setMinimumSize(265, 630); 26 | 27 | //Maximize browser window 28 | win.maximize(); 29 | 30 | 31 | // Serve our index.html file 32 | // mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`); 33 | win.loadFile('index.html'); 34 | 35 | // Add event listener to set our global window variable to null 36 | // This is needed so that window is able to reopen when user relaunches the app 37 | win.on('closed', () => { 38 | //added to overwrite .env with null every time window is closed so new import can work 39 | fs.writeFileSync(path.join(__dirname, '/.env'), "", 'utf8'); 40 | win = null; 41 | }); 42 | 43 | } 44 | 45 | // Creates our window when electron has initialized for the first time 46 | app.on('ready', createWindow); 47 | 48 | app.on('window-all-closed', () => { 49 | if (process.platform !== 'darwin') app.quit(); 50 | }); 51 | 52 | app.on('activate', () => { 53 | if (win === null) createWindow(); 54 | }); 55 | 56 | //---------------------APOLLO SERVER EXPORT ------------------------// 57 | 58 | // Overwrite default Apollo Server code files 59 | const createApolloFile = (filePath, data) => { 60 | try { 61 | fs.writeFileSync(path.join(__dirname, `/apollo-server/${filePath}`), data, 'utf8'); 62 | } catch (err) { 63 | return console.error(err); 64 | } 65 | } 66 | 67 | //function to run when user clicks export 68 | function showExportDialog(event, gqlSchema, gqlResolvers, sqlScripts, env, queries) { 69 | dialog.showOpenDialog( 70 | { 71 | title: 'Choose location to save folder in', 72 | defaultPath: app.getPath('desktop'), 73 | message: 'Choose location to save folder in', 74 | properties: ['openDirectory'] 75 | }, 76 | result => { 77 | //if user closes dialog window without selecting a folder 78 | if (!result) return; 79 | 80 | // creates the files in the apollo-sever folder 81 | createApolloFile('graphql/schema.js', gqlSchema); 82 | createApolloFile('graphql/resolvers.js', gqlResolvers); 83 | createApolloFile('db/createTables.sql', sqlScripts); 84 | createApolloFile('.env', env); 85 | //generate tests 86 | createApolloFile('tests/tests.js', buildExportTestSuite.createTest(queries[0], queries[1])); 87 | 88 | 89 | const output = fs.createWriteStream(result + '/apollo-server.zip', 90 | // { autoClose: false } 91 | ); 92 | const archive = archiver('zip', { 93 | zlib: { level: 9 } // Sets the compression level. 94 | }); 95 | 96 | // good practice to catch warnings (ie stat failures and other non-blocking errors) 97 | archive.on('warning', function (err) { 98 | if (err.code === 'ENOENT') console.error(err) 99 | else throw err; 100 | }); 101 | 102 | archive.on('error', function (err) { 103 | throw err; 104 | }); 105 | 106 | // append files from apollo-server directory and naming it `apollo-server` within the archive 107 | archive.directory(__dirname + '/apollo-server/', 'apollo-server'); 108 | 109 | // pipe the archive details to our zip file -> pushes files into the zip 110 | archive.pipe(output); 111 | 112 | // finalize the archive (ie we are done appending files but streams have to finish yet) 113 | // 'close' will be fired afterwards 114 | archive.finalize(); 115 | 116 | 117 | // listen for all archive data to be written and output associated details 118 | output.on('close', function () { 119 | console.log('Zip file size is ', archive.pointer() + ' total bytes'); 120 | console.log('Archived zip file is complete.'); 121 | 122 | //reverts templates to empty files for future use 123 | createApolloFile('graphql/schema.js', ''); 124 | createApolloFile('graphql/resolvers.js', ''); 125 | createApolloFile('db/createTables.sql', ''); 126 | createApolloFile('.env', ''); 127 | createApolloFile('tests/tests.js','') 128 | 129 | dialog.showMessageBox(win, 130 | { 131 | type: "info", 132 | buttons: ["Ok"], 133 | message: "Export Successful!", 134 | detail: 'File saved to ' + result + '/apollo-server.zip' 135 | } 136 | ) 137 | }); 138 | } 139 | ); 140 | } 141 | 142 | //The function to create the test file 143 | const createTestFile = (filePath, data) => { 144 | try { 145 | //write to a file and replace if it already exists 146 | fs.writeFileSync(path.join(__dirname, `/tests/${filePath}`), data, 'utf8'); 147 | } catch (err) { 148 | return console.error(err); 149 | } 150 | } 151 | 152 | 153 | //---------------------TEST EXPORT -------------------// 154 | 155 | 156 | //function to run when user clicks "Export Tests" 157 | function showTestExportDialog(event, queries) { 158 | dialog.showOpenDialog( 159 | { 160 | title: 'Choose location to save file', 161 | defaultPath: app.getPath('desktop'), 162 | message: 'Choose location to save file', 163 | properties: ['openDirectory'] 164 | }, 165 | result => { 166 | //if user closes dialog window without selecting a folder 167 | if (!result) return; 168 | 169 | // creates the files in the tests folder 170 | // createTestFile('.env', env); 171 | //generate tests 172 | console.log(queries); 173 | createTestFile('tests.js', buildExportTestSuite.createTest(queries[0], queries[1])); 174 | 175 | //make a new output function that is an fs WRITE with the same data. 176 | fs.writeFile(result + '/tests.js', buildExportTestSuite.createTest(queries[0], queries[1]),function (err) { 177 | if (err) throw err;}); 178 | createTestFile('tests.js','') 179 | 180 | dialog.showMessageBox(win, 181 | { 182 | type: "info", 183 | buttons: ["Ok"], 184 | message: "Export Successful!", 185 | detail: 'File saved to ' + result + '/tests.js' 186 | } 187 | ) 188 | }); 189 | } 190 | 191 | //listener for Apollo Server export button being clicked 192 | ipcMain.on('show-export-dialog', (event, gqlSchema, gqlResolvers, sqlScripts, env, queries) => { 193 | console.log('show-export-dialog => ', queries); 194 | showExportDialog(event, gqlSchema, gqlResolvers, sqlScripts, env, queries); 195 | }); 196 | 197 | //listener for test export button being clicked 198 | ipcMain.on('show-test-export-dialog', (event, queries) => { 199 | console.log('show-test-export-dialog => ', queries); 200 | showTestExportDialog(event, queries); 201 | }); 202 | 203 | 204 | //--------------------- IMPORT POSTGRES TABLES -----------------// 205 | 206 | // function importTables(event, gqlSchema, gqlResolvers, sqlScripts, env){ 207 | // pgQuery(); 208 | // } 209 | 210 | ipcMain.on('import-tables', (event, env) => { 211 | let tables; 212 | pgQuery(tables) 213 | .then((tables) => { 214 | // console.log("tables (main):", tables) 215 | event.reply('tables-imported', tables) 216 | }) 217 | .catch(err => console.error("Error importing tables from postgres")) 218 | }) 219 | //--------------------- CREATE ENV FILE -------------------// 220 | 221 | async function createEnvFile(env) { 222 | try { 223 | fs.writeFileSync(path.join(__dirname, '/.env'), env, 'utf8') 224 | } catch (err) { 225 | return console.error(err); 226 | } 227 | } 228 | 229 | ipcMain.on('create-env-file', (event, env) => { 230 | console.log('create env file URI: ', env); 231 | createEnvFile(env) 232 | .then(res => { 233 | event.reply('env-file-created'); 234 | console.log('env-file-created') 235 | }) 236 | .catch(err => { 237 | console.log('error occurred in createEnvFile promise') 238 | }) 239 | }); 240 | 241 | //--------------------- MENU CUSTOMIZATION -------------------// 242 | 243 | // customizes the about option in the menu bar 244 | const originalTeam = 'Alena Budzko, Bryan Fong, Rodolfo Guzman, Jarred Jack Harewood, Geoffrey Lin'; 245 | const contributors = 'Haris Hambasic, Michelle Moody, Jessica Vaughan, Vance Wallace'; 246 | app.setAboutPanelOptions({applicationName: 'ProtoGraphQL', applicationVersion: '2.0', copyright: 'MIT License', credits: `Original Team\n${originalTeam}\n\nAdditional Contributors\n${contributors}`, website: 'https://github.com/oslabs-beta/protographql', iconPath: './public/assets/pictures/icon/icon.png'}); 247 | 248 | // set options for custom menu feature 249 | const protographqlHelp = { 250 | buttons: ['OK'], 251 | message: 'Add Table\nCreate tables that mimic PSQL tables\n\nSchema\nView, edit or delete tables\n\nCode\nView generated GraphQL and SQL code before export\n\nVisualize\nView the GraphQL schema intuitively as a simple tree\n\nExport\nExport project to interact with database', 252 | title: 'ProtoGraphQL Help', 253 | type: 'info', 254 | icon: './public/assets/pictures/icon/icon.png', 255 | normalizeAccessKeys: true, 256 | } 257 | 258 | // the template and functions required to customize the menu bar - use the following format to add functionality { label: 'Swim', click () { 'clicked swim'}}, 259 | const template = [ 260 | // { role: 'appMenu' } 261 | ...(process.platform === 'darwin' ? [{ 262 | label: app.getName(), 263 | submenu: [ 264 | { role: 'about' }, 265 | { type: 'separator' }, 266 | { role: 'services' }, 267 | { type: 'separator' }, 268 | { role: 'hide' }, 269 | { role: 'hideothers' }, 270 | { role: 'unhide' }, 271 | { type: 'separator' }, 272 | { role: 'quit' } 273 | ] 274 | }] : []), 275 | // { role: 'fileMenu' } 276 | { 277 | label: 'File', 278 | submenu: [ 279 | process.platform === 'darwin' ? { role: 'close' } : { role: 'quit' }, 280 | ] 281 | }, 282 | // { role: 'editMenu' } 283 | { 284 | label: 'Edit', 285 | submenu: [ 286 | { role: 'undo' }, 287 | { role: 'redo' }, 288 | { type: 'separator' }, 289 | { role: 'cut' }, 290 | { role: 'copy' }, 291 | { role: 'paste' }, 292 | ...(process.platform === 'darwin' ? [ 293 | { role: 'pasteAndMatchStyle' }, 294 | { role: 'delete' }, 295 | { role: 'selectAll' }, 296 | { type: 'separator' }, 297 | { 298 | label: 'Speech', 299 | submenu: [ 300 | { role: 'startspeaking' }, 301 | { role: 'stopspeaking' } 302 | ] 303 | } 304 | ] : [ 305 | { role: 'delete' }, 306 | { type: 'separator' }, 307 | { role: 'selectAll' } 308 | ]) 309 | ] 310 | }, 311 | // { role: 'viewMenu' } 312 | { 313 | label: 'View', 314 | submenu: [ 315 | { role: 'reload' }, 316 | { role: 'forcereload' }, 317 | { role: 'toggledevtools' }, 318 | { type: 'separator' }, 319 | { role: 'resetzoom' }, 320 | { role: 'zoomin' }, 321 | { role: 'zoomout' }, 322 | { type: 'separator' }, 323 | { role: 'togglefullscreen' } 324 | ] 325 | }, 326 | // { role: 'windowMenu' } 327 | { 328 | label: 'Window', 329 | submenu: [ 330 | { role: 'minimize' }, 331 | { role: 'zoom' }, 332 | ...(process.platform === 'darwin' ? [ 333 | { type: 'separator' }, 334 | { role: 'front' }, 335 | { type: 'separator' }, 336 | { role: 'window' } 337 | ] : [ 338 | { role: 'close' } 339 | ]) 340 | ] 341 | }, 342 | { 343 | role: 'help', 344 | submenu: [ 345 | { label: 'ProtoGraphQL Help', click () { dialog.showMessageBox(protographqlHelp) }}, 346 | { 347 | label: 'ProtoGraphQL on GitHub', 348 | click: async () => { 349 | await shell.openExternal('https://github.com/oslabs-beta/protographql') 350 | } 351 | }, 352 | { type: 'separator' }, 353 | { 354 | label: 'Learn More About Electron', 355 | click: async () => { 356 | await shell.openExternal('https://electronjs.org') 357 | } 358 | } 359 | ] 360 | } 361 | ] 362 | 363 | const menu = Menu.buildFromTemplate(template); 364 | Menu.setApplicationMenu(menu); 365 | 366 | 367 | 368 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protographql", 3 | "version": "1.0.0", 4 | "description": "ProtGraphQl is prototyping tool to easily build and visualize GraphQl schemas and queries", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "build": "webpack -p", 9 | "dev": "webpack-dev-server --mode=development", 10 | "dist": "build", 11 | "package-all": "build && electron-builder build -mwl", 12 | "test": "jest" 13 | }, 14 | "build": { 15 | "productName": "ProtoGraphQL", 16 | "appId": "org.oslabs.Protographql.todo", 17 | "asar": false, 18 | "files": [ 19 | "public/", 20 | "index.html", 21 | "main.js", 22 | "apollo-server/", 23 | "package.json" 24 | ], 25 | "dmg": { 26 | "contents": [ 27 | { 28 | "x": 130, 29 | "y": 220 30 | }, 31 | { 32 | "x": 410, 33 | "y": 220, 34 | "type": "link", 35 | "path": "/Applications" 36 | } 37 | ] 38 | }, 39 | "win": { 40 | "target": [ 41 | "nsis", 42 | "msi" 43 | ] 44 | }, 45 | "linux": { 46 | "target": [ 47 | "deb", 48 | "rpm", 49 | "snap", 50 | "AppImage" 51 | ], 52 | "category": "Development" 53 | }, 54 | "directories": { 55 | "buildResources": "public/assets/pictures/icon", 56 | "output": "release" 57 | }, 58 | "publish": { 59 | "provider": "github", 60 | "owner": "oslabs-beta", 61 | "repo": "protographql", 62 | "private": false 63 | } 64 | }, 65 | "repository": { 66 | "type": "git", 67 | "url": "git+https://github.com/protographql/protographql.git" 68 | }, 69 | "author": { 70 | "name": "ProtoGraphQL", 71 | "email": "protographql-labs@todo.com", 72 | "url": "https://protographql.todo.org" 73 | }, 74 | "contributors": [ 75 | { 76 | "name": "Alena Budzko", 77 | "email": "#TODO", 78 | "url": "#TODO" 79 | }, 80 | { 81 | "name": "Bryan Fong", 82 | "email": "", 83 | "url": "" 84 | }, 85 | { 86 | "name": "Rodolfo Guzman", 87 | "email": "", 88 | "url": "" 89 | }, 90 | { 91 | "name": "Jarred Jack-Harewood", 92 | "email": "", 93 | "url": "" 94 | }, 95 | { 96 | "name": "Geoffrey Lin", 97 | "email": "", 98 | "url": "" 99 | } 100 | ], 101 | "license": "ISC", 102 | "bugs": { 103 | "url": "https://github.com/protographql/protographql/issues" 104 | }, 105 | "homepage": "https://github.com/protographql/protographql#readme", 106 | "devDependencies": { 107 | "@babel/core": "^7.4.5", 108 | "@babel/preset-env": "^7.6.0", 109 | "@babel/preset-react": "^7.0.0", 110 | "@material-ui/core": "^4.0.2", 111 | "awesome-typescript-loader": "^5.2.1", 112 | "babel-loader": "^8.0.6", 113 | "css-loader": "^2.1.1", 114 | "d3": "^5.9.2", 115 | "electron": "^5.0.10", 116 | "enzyme": "^3.9.0", 117 | "eslint": "^5.16.0", 118 | "eslint-plugin-react": "^7.13.0", 119 | "express": "^4.17.1", 120 | "jest": "^24.8.0", 121 | "nodemon": "^1.19.1", 122 | "source-map-loader": "^0.2.4", 123 | "styled-components": "^4.3.1", 124 | "ts-loader": "^6.0.2", 125 | "typescript": "^3.5.1", 126 | "webpack": "^4.39.2", 127 | "webpack-cli": "^3.3.2", 128 | "webpack-dev-server": "^3.5.1" 129 | }, 130 | "dependencies": { 131 | "@apollo/react-hooks": "^3.0.1", 132 | "@babel/plugin-transform-runtime": "^7.6.0", 133 | "@babel/cli": "^7.6.0", 134 | "@babel/plugin-proposal-class-properties": "^7.5.5", 135 | "@babel/plugin-proposal-export-default-from": "^7.5.2", 136 | "@babel/polyfill": "^7.6.0", 137 | "@material-ui/icons": "^4.0.1", 138 | "@types/react": "^16.8.19", 139 | "@types/react-dom": "^16.8.4", 140 | "apollo-boost": "^0.4.4", 141 | "apollo-cache-inmemory": "^1.6.3", 142 | "apollo-link": "^1.2.12", 143 | "apollo-link-error": "^1.1.11", 144 | "apollo-server-express": "^2.9.0", 145 | "archiver": "^3.0.0", 146 | "babel-jest": "^24.9.0", 147 | "dotenv": "^8.1.0", 148 | "graphiql": "^0.14.2", 149 | "graphql": "^14.5.3", 150 | "graphql-tag": "^2.10.1", 151 | "graphql-transport-electron": "^1.0.1", 152 | "isomorphic-fetch": "^2.2.1", 153 | "path": "^0.12.7", 154 | "pg": "^7.12.1", 155 | "react": "^16.9.0", 156 | "react-dom": "^16.9.0", 157 | "react-draggable": "^3.3.0" 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /palette.css: -------------------------------------------------------------------------------- 1 | /* This is just a reference file not actually imported anywhere */ 2 | 3 | /* Coolors Exported Palette - coolors.co/b7cadc-e5e7e9-324353-fefefe-dd399c */ 4 | 5 | /* HSL */ 6 | HSL { 7 | color1: hsla(209%, 35%, 79%, 1); 8 | color2: hsla(210%, 6%, 94%, 1); 9 | color3: hsla(209%, 25%, 26%, 1); 10 | color4: hsla(0%, 0%, 100%, 1); 11 | color5: hsla(324%, 71%, 55%, 1); 12 | } 13 | 14 | /* RGB */ 15 | RGB { 16 | color1: rgba(183, 202, 220, 1); 17 | color2: rgba(238, 239, 240, 1); 18 | color3: rgba(50, 67, 83, 1); 19 | color4: rgba(254, 254, 254, 1); 20 | color5: rgba(221, 57, 156, 1); 21 | } -------------------------------------------------------------------------------- /public/assets/pictures/Code_Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/Code_Screenshot.png -------------------------------------------------------------------------------- /public/assets/pictures/Export_Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/Export_Screenshot.png -------------------------------------------------------------------------------- /public/assets/pictures/GitHub-Mark-Light-64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/GitHub-Mark-Light-64px.png -------------------------------------------------------------------------------- /public/assets/pictures/GraphQL-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/GraphQL-Logo.png -------------------------------------------------------------------------------- /public/assets/pictures/GraphQL_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/GraphQL_Logo.png -------------------------------------------------------------------------------- /public/assets/pictures/ProtoGraphQLLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/ProtoGraphQLLogo.png -------------------------------------------------------------------------------- /public/assets/pictures/ProtoGraphQLLogo64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/ProtoGraphQLLogo64.png -------------------------------------------------------------------------------- /public/assets/pictures/ProtographQLBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/ProtographQLBanner.png -------------------------------------------------------------------------------- /public/assets/pictures/Rest-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/Rest-Logo.png -------------------------------------------------------------------------------- /public/assets/pictures/Schema_Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/Schema_Screenshot.png -------------------------------------------------------------------------------- /public/assets/pictures/add-table demo.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/add-table demo.mov.gif -------------------------------------------------------------------------------- /public/assets/pictures/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/icon.png -------------------------------------------------------------------------------- /public/assets/pictures/icon/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/icon/icon.icns -------------------------------------------------------------------------------- /public/assets/pictures/icon/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/icon/icon.ico -------------------------------------------------------------------------------- /public/assets/pictures/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/icon/icon.png -------------------------------------------------------------------------------- /public/assets/pictures/tree visializer demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/tree visializer demo.gif -------------------------------------------------------------------------------- /public/assets/pictures/v2-add-tables.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/v2-add-tables.gif -------------------------------------------------------------------------------- /public/assets/pictures/v2-codeview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/v2-codeview.png -------------------------------------------------------------------------------- /public/assets/pictures/v2-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/v2-export.png -------------------------------------------------------------------------------- /public/assets/pictures/v2-import-tables.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/v2-import-tables.gif -------------------------------------------------------------------------------- /public/assets/pictures/v2-schema-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/v2-schema-view.png -------------------------------------------------------------------------------- /public/assets/pictures/v2-test-queries.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/v2-test-queries.gif -------------------------------------------------------------------------------- /public/assets/pictures/v2-visualizer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/public/assets/pictures/v2-visualizer.gif -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ProtoGraphQL 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/styles.css: -------------------------------------------------------------------------------- 1 | /* CSS RESET */ 2 | html, body, div, span, applet, object, iframe, 3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 4 | a, abbr, acronym, address, big, cite, code, 5 | del, dfn, em, img, ins, kbd, q, s, samp, 6 | small, strike, strong, sub, sup, tt, var, 7 | b, u, i, center, 8 | dl, dt, dd, ol, ul, li, 9 | fieldset, form, label, legend, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | /* defines the amount of space above and below inline elements */ 28 | line-height: 1; 29 | } 30 | ol, ul { 31 | /* specifies the type of list-item marker in a list */ 32 | list-style: none; 33 | } 34 | blockquote, q { 35 | /* sets the type of quotation marks for quotations */ 36 | quotes: none; 37 | } 38 | blockquote:before, blockquote:after, 39 | q:before, q:after { /* content may be only used in association with :before and :after */ 40 | /* insert generated content */ 41 | content: ''; 42 | /* insert generated content */ 43 | content: none; 44 | } 45 | table { 46 | /* sets whether cells inside a table have shared or separate borders */ 47 | border-collapse: collapse; 48 | /* sets the distance between the borders of adjacent cells */ 49 | border-spacing: 0; 50 | } 51 | 52 | 53 | /******************/ 54 | /* INPUT BOXES */ 55 | /******************/ 56 | 57 | /* style elements when they are in focus */ 58 | input:focus { 59 | /* does not draw a line around the outside of an element */ 60 | outline: none; 61 | } 62 | 63 | /******************/ 64 | /* Slider Buttons */ 65 | /******************/ 66 | 67 | .other-header{ 68 | width: 86px; 69 | } 70 | 71 | .slider-header { 72 | width: 55px; 73 | } 74 | 75 | .switch { 76 | position: relative; 77 | display: inline-block; 78 | width: 60px; 79 | height: 34px; 80 | } 81 | 82 | .switch input { 83 | opacity: 0; 84 | width: 0; 85 | height: 0; 86 | } 87 | 88 | .slider { 89 | position: absolute; 90 | cursor: pointer; 91 | top: 0; 92 | left: 0; 93 | right: 0; 94 | bottom: 0; 95 | background-color: #ccc; 96 | -webkit-transition: .4s; 97 | transition: .4s; 98 | } 99 | 100 | .slider:before { 101 | position: absolute; 102 | content: ""; 103 | height: 26px; 104 | width: 26px; 105 | left: 4px; 106 | bottom: 4px; 107 | background-color: white; 108 | -webkit-transition: .4s; 109 | transition: .4s; 110 | } 111 | 112 | input:checked + .slider { 113 | background-color: #2196F3; 114 | } 115 | 116 | input:focus + .slider { 117 | box-shadow: 0 0 1px #2196F3; 118 | } 119 | 120 | input:checked + .slider:before { 121 | -webkit-transform: translateX(26px); 122 | -ms-transform: translateX(26px); 123 | transform: translateX(26px); 124 | } 125 | 126 | /* Rounded sliders */ 127 | .slider.round { 128 | border-radius: 34px; 129 | } 130 | 131 | .slider.round:before { 132 | border-radius: 50%; 133 | } 134 | 135 | 136 | /* Snackbar - cause we hungry */ 137 | 138 | #snackbar { 139 | text-align: center; 140 | visibility: hidden; 141 | min-width: 250px; 142 | margin-left: -125px; 143 | background-color: #333; 144 | color: #fff; 145 | border-radius: 2px; 146 | padding: 16px; 147 | position: fixed; 148 | z-index: 9999; 149 | right: 30px; 150 | top: 100px; 151 | font-size: 17px; 152 | opacity: .9; 153 | } 154 | 155 | #snackbar.show { 156 | visibility: visible; 157 | -webkit-animation: fadein 0.5s, fadeout 0.5s 3s; 158 | animation: fadein 0.5s, fadeout 0.5s 3s; 159 | } 160 | 161 | @-webkit-keyframes fadein { 162 | from {right: 0; opacity: 0;} 163 | to {right: 30px; opacity: .9;} 164 | } 165 | 166 | @keyframes fadein { 167 | from {right: 0; opacity: 0;} 168 | to {right: 30px; opacity: .9;} 169 | } 170 | 171 | @-webkit-keyframes fadeout { 172 | from {right: 30px; opacity: .9;} 173 | to {right: 0; opacity: 0;} 174 | } 175 | 176 | @keyframes fadeout { 177 | from {right: 30px; opacity: .9;} 178 | to {right: 0; opacity: 0;} 179 | } 180 | 181 | #error { 182 | font-family: "Roboto", sans-serif; 183 | font-size: 13px; 184 | text-align: left; 185 | color:#DD399C; 186 | letter-spacing: 1px; 187 | padding: 2px; 188 | width: 500px; 189 | margin-bottom: 10px; 190 | } 191 | 192 | .invisible { 193 | visibility: hidden; 194 | } -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Main from './container/mainContainer'; 3 | import { StoreProvider } from './state/store'; 4 | 5 | export const App = () => ( 6 | 7 |
8 | 9 | ); -------------------------------------------------------------------------------- /src/actions/actionTypes.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | This file declares the action types to be used by store. 3 | */ 4 | 5 | export const ADD_TABLE = "ADD_TABLE"; 6 | export const EDIT_TABLE = "EDIT_TABLE"; 7 | export const SAVE_TABLE = "SAVE_TABLE"; 8 | export const EDIT_TABLE_NAME = "EDIT_TABLE_NAME"; 9 | export const DELETE_TABLE = "DELETE_TABLE"; 10 | export const IMPORT_TABLES = "IMPORT_TABLES"; 11 | 12 | export const EDIT_FIELD = "EDIT_FIELD"; 13 | export const ADD_FIELD = "ADD_FIELD"; 14 | export const DELETE_FIELD = "DELETE_FIELD"; 15 | export const EDIT_RELATIONS = "EDIT_RELATIONS"; 16 | 17 | export const SET_POP_UP = "SET_POP_UP"; 18 | export const SET_VIEW = "SET_VIEW"; 19 | 20 | export const HIDE_DISPLAY_ERROR = "HIDE_DISPLAY_ERROR"; 21 | export const THROTTLE_DISPLAY_ERROR = "THROTTLE_DISPLAY_ERROR"; 22 | 23 | export const UPDATE_QUERIES = "UPDATE_QUERIES"; 24 | 25 | export const ADD_APOLLO_SERVER_URI = "ADD_APOLLO_SERVER_URI"; 26 | -------------------------------------------------------------------------------- /src/components/header/header.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | This file defines the header of the application 3 | */ 4 | 5 | import React, { useContext } from 'react'; 6 | import { Store } from '../../state/store'; 7 | import { SET_POP_UP } from '../../actions/actionTypes'; 8 | import { makeStyles } from '@material-ui/core/styles'; 9 | import { 10 | AppBar, 11 | Toolbar, 12 | Typography, 13 | Button, 14 | IconButton 15 | } from '@material-ui/core'; 16 | import { isWhiteSpaceLike } from 'typescript'; 17 | 18 | 19 | 20 | /*-------------------- Styled components --------------------*/ 21 | 22 | const useStyles = makeStyles(theme => ({ 23 | root: { 24 | flexGrow: 3, 25 | gridArea: "header", 26 | }, 27 | // styles the complete title of the application, ProtoGraphQL 28 | menuButton: { 29 | marginRight: theme.spacing(2) 30 | }, 31 | // styles the Proto part of the title of the application, ProtoGraphQL 32 | title: { 33 | flexGrow: 2 34 | }, 35 | header: { 36 | backgroundColor: "#324353", 37 | }, 38 | // style the GraphQL part of the title of the application, ProtoGraphQL 39 | pink: { 40 | color: "#DD399C", 41 | }, 42 | // style the Import and Export buttons in the header 43 | button: { 44 | color: 'white', 45 | backgroundColor: '#324353', 46 | border: '1px solid white', 47 | marginRight: '10px', 48 | minWidth: '50px', 49 | "&:hover": { 50 | backgroundColor: '#EEEFF0', 51 | transform: 'scale(1.01)', 52 | color: '#324353', 53 | border: '1px solid #324353', 54 | } 55 | }, 56 | // style the GitHub button in the header 57 | anchor: { 58 | color: 'white', 59 | "&:hover": { 60 | color: '#324353', 61 | } 62 | }, 63 | // style the text associated with each button on the right side of the header 64 | label: { 65 | display: 'inline-block', 66 | fontSize: '10px', 67 | paddingLeft: '5px', 68 | } 69 | })); 70 | 71 | /*-------------------- Functional Component --------------------*/ 72 | 73 | function Header() { 74 | // sets the classes to be used in the header, as defined on line 22 75 | const classes = useStyles(); 76 | /* 77 | -> connects the application to the context (utilized by Hooks in React) and facilitates the ability to 78 | update the context of the application 79 | -> the context is initialized by useContext() and specified by Store which is found 80 | in /components/state/store.jsx 81 | */ 82 | const { state: { popUp }, dispatch } = useContext(Store); 83 | 84 | return ( 85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | Proto 93 | GraphQL 94 | 95 | 99 | 103 | 109 | 110 | 111 |
112 | ); 113 | } 114 | 115 | export default Header; 116 | -------------------------------------------------------------------------------- /src/components/popup/exportPopUp.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React, { useContext } from "react"; 3 | import { 4 | Button, 5 | Dialog, 6 | DialogActions, 7 | DialogTitle, 8 | } from "@material-ui/core"; 9 | import { styled } from "@material-ui/styles"; 10 | import { Store } from '../../state/store'; 11 | import { SET_POP_UP, SET_VIEW, IMPORT_TABLES } from '../../actions/actionTypes'; 12 | import buildENV from '../../utils/buildENV'; 13 | import TextField from '@material-ui/core/TextField'; 14 | import { IpcLink } from "graphql-transport-electron"; 15 | 16 | //comment out to use web-dev-server instead of electron 17 | const electron = window.require('electron'); 18 | const ipc = electron.ipcRenderer; 19 | 20 | /*-------------------- Styled components --------------------*/ 21 | 22 | // styles the header of the Export button of the header of the application 23 | const Title = styled(DialogTitle)({ 24 | width: "500px", 25 | textAlign: "center", 26 | padding: '25px', 27 | color: 'white', 28 | background: '#161e26', 29 | }); 30 | 31 | // styles the buttons of the Export button of the header of the application 32 | const StyledButton = styled(Button)({ 33 | width: '200px', 34 | border: '1px solid #161e26', 35 | color: '#161e26', 36 | padding: '10px', 37 | margin: '10px' 38 | }); 39 | 40 | // styles the entire dialog box associated with the Export button of the header of the application 41 | const DialogActionsDiv = styled(DialogActions)({ 42 | justifyContent: 'center', 43 | margin: 0, 44 | }); 45 | 46 | /* 47 | styles the input field of the dialog box associated with the Export button of the header 48 | of the application 49 | */ 50 | const Input = styled(TextField)({ 51 | margin: '0px', 52 | marginTop: '20px', 53 | width: 500 54 | }) 55 | 56 | /*-------------------- Functional Component --------------------*/ 57 | 58 | function ExportPopUp(props) { 59 | 60 | const { state: { popUp, gqlSchema, gqlResolvers, sqlScripts, queries }, dispatch } = useContext(Store); 61 | 62 | // this function is invoked on the 'Close' button on a click event, as defined on line 104 63 | const handleClose = () => { 64 | dispatch({ type: SET_POP_UP, payload: '' }); 65 | } 66 | 67 | // handles key commands to enable mouse-less / pad-less interaction with the application 68 | const keyUpToHandleClose = (e) => { 69 | // this condition is true when a user presses the 'Escape' button on a keyboad 70 | if (e.keyCode === 27) { 71 | /* 72 | invoke dispatch(), passing in a object type of 'SET_POP_UP' and a payload of '' to 73 | update the store 74 | */ 75 | dispatch({ type: SET_POP_UP, payload: '' }); 76 | // this condition is true when a user presses the 'Enter' button on a keyboad 77 | } else if (e.keyCode === 13) { 78 | // select the DOM element with an ID of 'export' and initiate a 'click' event on it 79 | document.querySelector('#export').click(); 80 | } 81 | } 82 | 83 | return ( 84 |
85 | 86 | E X P O R T 87 | 88 |
{ 91 | e.preventDefault(); 92 | const uri = e.target.childNodes[0].childNodes[1].childNodes[0].value; 93 | if (uri.slice(0, 11).toLowerCase() === 'postgres://' || uri.slice(0, 13).toLowerCase() === 'postgresql://') { 94 | // emitting message to electron window to open save dialog 95 | ipc.send('show-export-dialog', gqlSchema, gqlResolvers, sqlScripts, buildENV(uri), queries); 96 | dispatch({ type: SET_POP_UP, payload: '' }); 97 | } else { 98 | document.querySelector('#error').classList.remove('invisible') 99 | } 100 | }}> 101 | 102 |

URI needs to start with "postgres://" or "postgresql://"

103 | 104 | Export 105 | Close 106 | 107 | 108 |
109 |
110 | ); 111 | } 112 | 113 | export default ExportPopUp; 114 | -------------------------------------------------------------------------------- /src/components/popup/instructions.jsx: -------------------------------------------------------------------------------- 1 | //These are the instructions 2 | 3 | /* eslint-disable no-unused-vars */ 4 | import React, { useContext } from "react"; 5 | import { 6 | Button, 7 | Dialog, 8 | DialogActions, 9 | DialogContent, 10 | DialogContentText, 11 | DialogTitle, 12 | Paper 13 | } from "@material-ui/core"; 14 | import Draggable from "react-draggable"; 15 | import { styled } from "@material-ui/styles"; 16 | import { Store } from '../../state/store'; 17 | import { SET_POP_UP } from '../../actions/actionTypes'; 18 | 19 | /*-------------------- Styled components --------------------*/ 20 | 21 | // styles the header of this dialog-box 22 | const Title = styled(DialogTitle)({ 23 | width: "500px", 24 | textAlign: "center", 25 | padding: '25px', 26 | color: 'white', 27 | background: '#161e26', 28 | }); 29 | 30 | 31 | // styles "ContentDiv" 32 | const ContentDiv = styled(DialogContent)({ 33 | textAlign: 'left', 34 | width: '500px', 35 | color: '#161e26', 36 | }); 37 | 38 | // styles the "GotItButton" 39 | const GotItButton = styled(Button)({ 40 | width: '100px', 41 | border: '1px solid #161e26', 42 | // color: '#161e26', 43 | padding: '10px', 44 | marginTop: '0px', 45 | marginBottom: '10px', 46 | color: "rgba(221, 57, 156, 1)", 47 | fontFamily: "Roboto, sans-serif", 48 | }); 49 | 50 | /* 51 | styles the space around the GotItButton button 52 | */ 53 | const DialogActionsDiv = styled(DialogActions)({ 54 | justifyContent: 'center', 55 | margin: 0 56 | }); 57 | 58 | /*-------------------- Functional Component --------------------*/ 59 | 60 | function PaperComponent(props) { 61 | return ( 62 | 63 | 64 | 65 | ); 66 | } 67 | 68 | function DraggableDialog(props) { 69 | 70 | //USE CONTEXT 71 | const { state: { popUp }, dispatch } = useContext(Store); 72 | 73 | const handleClose = () => { 74 | dispatch({ type: SET_POP_UP, payload: '' }); 75 | } 76 | 77 | const keyUpToHandleClose = (e) => { 78 | // condition that handles the 'Escape' and 'Enter' buttons on a keyboard 79 | if (e.keyCode == 13 || e.keyCode == 27) { 80 | /* 81 | invoke 'dispatch' when 'Escape' or 'Enter' is pressed and pass the object type of 82 | 'SET_POP_UP' and a payload of '' 83 | */ 84 | dispatch({ type: SET_POP_UP, payload: '' }); 85 | } 86 | } 87 | 88 | //END OF USE CONTEXT 89 | 90 | return ( 91 |
92 | 93 | Instructions 94 | 95 | 96 | 1) Add the endpoint of your running GraphQL server in the "GraphQL Endpoint URL" box. Click the "Add URL" button 97 | 2) If no valid endpoint is entered, the default endpoint will be set to http://localhost:3000/GraphQL 98 | 3) Type your test query in the box on the left. 99 | 4) Click the "Add Query" button
(Repeat steps 2 and 3 to add additional query response pairs)
100 | 5) To export your test file click the "Export Tests" button. 101 |
102 | 103 | Got it! 104 | 105 |
106 |
107 | ); 108 | } 109 | 110 | export default DraggableDialog; 111 | -------------------------------------------------------------------------------- /src/components/popup/tableField.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | /*-------------------- Styled Components --------------------*/ 5 | 6 | /* 7 | styles the table row... in this application and in this instance, the only item styles is the line 8 | between 'Enter Table Name' and the titles of the options associated with the 'Add Table' button 9 | */ 10 | const Tr = styled.tr` 11 | border-top: 1px solid rgba(0, 0, 0, 0.2); 12 | `; 13 | 14 | /* 15 | styles the table headers... in this application and in this instance, the items that are styled are the 16 | titles associated with each option of the 'Add Table' button 17 | */ 18 | const Th = styled.th` 19 | font-size: .75em; 20 | padding: .9em; 21 | `; 22 | 23 | /*-------------------- Functional Component --------------------*/ 24 | 25 | function TableField() { 26 | return ( 27 | // this specifies the name of each option associated with the 'Add Table' button 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | export default TableField; 45 | -------------------------------------------------------------------------------- /src/components/popup/tableForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Store } from '../../state/store'; 3 | import { 4 | SET_POP_UP, 5 | SAVE_TABLE, 6 | ADD_FIELD, 7 | DELETE_FIELD, 8 | EDIT_FIELD, 9 | EDIT_RELATIONS, 10 | EDIT_TABLE_NAME 11 | } from '../../actions/actionTypes'; 12 | import styled from 'styled-components'; 13 | import TableNameInput from './tableNameInput'; 14 | import TableField from './tableField'; 15 | import TableInput from './tableInput'; 16 | import Draggable from 'react-draggable'; 17 | 18 | /*-------------------- Styled Components --------------------*/ 19 | 20 | /* 21 | styles the table associated with the 'Add Table' button... specifically the options and buttons 22 | associated with the options 23 | */ 24 | const CustomTable = styled.form` 25 | height: auto; 26 | margin: 0 auto; 27 | min-width: 550px; 28 | max-width: 1000px; 29 | position: relative; 30 | background-color: white; 31 | box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.12); 32 | position: fixed; 33 | top: 50%; 34 | left: 18%; 35 | `; 36 | 37 | /* 38 | styles the table associated with the 'Add Table' button... specifically the area around the buttons 39 | 'Add Field' and 'Save' 40 | */ 41 | const TableFooter = styled.div` 42 | border-top: 1px solid rgba(0,0,0,0.2); 43 | height: auto; 44 | margin: 0 auto; 45 | min-width: 700px; 46 | max-width: 1000px; 47 | position: relative; 48 | background-color: white; 49 | display: flex; 50 | flex-direction: row-reverse; 51 | justify-content: space-between; 52 | `; 53 | 54 | /* 55 | styles the table associated with the 'Add Table' button... specifically the options and buttons 56 | associated with the options 57 | */ 58 | const Table = styled.table` 59 | width: 100%; 60 | border-collapse: collapse; 61 | `; 62 | 63 | /* 64 | specifies the space around the 'X' icon located at the top-right of the dialog-box associated with 65 | the 'Add Table' button... this space is a block-level element 66 | */ 67 | const TableHeader = styled.div` 68 | height: 15px; 69 | padding-top: 4px; 70 | padding-bottom: 4px; 71 | width: 100%; 72 | background: rgba(50,67,83,1); 73 | cursor: move; 74 | `; 75 | 76 | // styles the background-page after the 'Add Table' button is clicked 77 | const FadeThePage = styled.div` 78 | position: fixed; 79 | top: 0; 80 | bottom: 0; 81 | left: 0; 82 | right: 0; 83 | z-index: 9998; 84 | background: rgba(90, 90, 90, 0.5); 85 | `; 86 | // styles the 'Add Field' and 'Save' buttons of the dialog-box associated with the 'Add Table' button 87 | const Button = styled.button` 88 | height: auto; 89 | font-size: .85em; 90 | font-weight: 300; 91 | margin: 8px; 92 | margin-right: 10px; 93 | margin-left: 10px; 94 | padding: 10px 0px; 95 | border-radius: 5px; 96 | box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.10); 97 | background-color: rgba(50, 67, 83, 0.85); 98 | color: white; 99 | width: 30%; 100 | text-align: center; 101 | &:hover { 102 | background-color: #DD399C; 103 | cursor: pointer; 104 | } 105 | `; 106 | // styles the 'X' icon on the top-right of the dialog-box associated with the 'Add Table' button 107 | const CloseButton = styled.span` 108 | font-size: 1em; 109 | margin: 5px; 110 | padding: 5px; 111 | color: white; 112 | &:hover { 113 | cursor: pointer; 114 | color: #DD399C; 115 | } 116 | `; 117 | 118 | // styles the 'X' icon on the top-right of the dialog-box associated with the 'Add Table' button 119 | const Buttons = styled.span` 120 | float: right; 121 | margin-right: 5px; 122 | `; 123 | 124 | /*-------------------- Functional Component --------------------*/ 125 | 126 | function TableForm() { 127 | 128 | const { dispatch, state: { selectedTable, tables } } = useContext(Store); 129 | 130 | /*-------------------- Table Input Function --------------------*/ 131 | const fieldInputs = []; 132 | const createTableInputs = () => { 133 | const fields = Object.keys(selectedTable.fields); 134 | for (let i = 0; i < fields.length; i++) { 135 | const currentFieldKey = fields[i]; 136 | fieldInputs.push( 137 | dispatch({ type: EDIT_FIELD, payload })} 140 | editRelations={payload => dispatch({ type: EDIT_RELATIONS, payload })} 141 | deleteField={payload => dispatch({ type: DELETE_FIELD, payload })} 142 | fieldIndex={currentFieldKey} 143 | key={selectedTable.tableID + "-" + "field" + "-" + currentFieldKey} 144 | tables={tables} 145 | />); 146 | } 147 | } 148 | createTableInputs(); 149 | 150 | /*------------- Enter / Esc Key to Close Pop Up ---------------*/ 151 | const keyUpToHandleClose = (e) => { 152 | if (e.keyCode == 27) { 153 | dispatch({ type: SET_POP_UP, payload: '' }); 154 | } 155 | } 156 | 157 | return ( 158 | 159 | 160 | { 161 | e.preventDefault(); 162 | dispatch({ type: SAVE_TABLE }); 163 | dispatch({ type: SET_POP_UP, payload: '' }); 164 | }}> 165 | 166 | 167 | dispatch({ type: SET_POP_UP, payload: '' })}> 168 | 169 | 170 | 171 | 172 | dispatch({ type: EDIT_TABLE_NAME, payload })} 175 | /> 176 |
DeleteField NameTypeDefault ValuePrimary KeyUniqueRequiredQueryableTable RelationshipField RelationshipType of Relationship
177 | 178 | 179 | {fieldInputs} 180 | 181 |
182 | 183 | 186 | 192 | 193 | 194 | 195 | 196 | ) 197 | } 198 | 199 | 200 | export default TableForm; 201 | -------------------------------------------------------------------------------- /src/components/popup/tableInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import styled from 'styled-components'; 3 | import deepClone from '../../utils/deepClone'; 4 | 5 | /*-------------------- Styled Components --------------------*/ 6 | 7 | /* 8 | styles the line between the options buttons and the space around the 'Add Field' and 'Save' buttons 9 | associated with the 'Add Table' button 10 | */ 11 | const Tr = styled.tr` 12 | border-top: 1px solid rgba(0, 0, 0, 0.2); 13 | vertical-align: middle; 14 | `; 15 | 16 | /* 17 | styles the buttons associated with the 'Type' and 'Table Relationship' options 18 | */ 19 | const Selected = styled.select` 20 | display: inline-flex; 21 | padding: .6em 1.4em .5em .8em; 22 | width: 90%; 23 | max-width: 100%; 24 | box-sizing: border-box; 25 | margin: 0; 26 | border: 1px solid #aaa; 27 | box-shadow: 0 1px 0 1px rgba(0,0,0,.04); 28 | border-radius: .5em; 29 | appearance: none; 30 | background-color: #fff; 31 | background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007CB2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'), 32 | linear-gradient(to bottom, #ffffff 0%,#e5e5e5 100%); 33 | background-repeat: no-repeat, repeat; 34 | background-position: right .7em top 50%, 0 0; 35 | background-size: .65em auto, 100%; 36 | &:disabled { 37 | opacity: .70; 38 | -webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */ 39 | filter: grayscale(100%); 40 | } 41 | `; 42 | 43 | // styles the trash icon of the dialog-box associated with the 'Add Table' button 44 | const Td = styled.td` 45 | font-size: .75em; 46 | text-align: center; 47 | margin: 0; 48 | padding-top: 5px; 49 | padding-bottom: 5px; 50 | &:hover{ 51 | color: #DD399C; 52 | } 53 | `; 54 | 55 | /* 56 | styles the input fields associated with the 'Field Name' and 'Default Value' options of the dialog-box 57 | associated with the 'Add Table' button 58 | */ 59 | const Input = styled.input` 60 | height: 2.5em; 61 | border-radius: 5px; 62 | margin: 0; 63 | width: 90%; 64 | margin: 5px; 65 | font-size: .75em; 66 | `; 67 | 68 | // styles the 'trash' icon of the dialog-box associated with the 'Add Table' button 69 | const TrashCan = styled.span` 70 | font-size: 18px; 71 | &:hover { 72 | color: #DD399C; 73 | cursor: pointer; 74 | } 75 | `; 76 | 77 | /*-------------------- Functional Component --------------------*/ 78 | 79 | function TableInput({ 80 | field, 81 | fieldIndex, // this represents the index of the field of the table 82 | editField, // this is used when there is a change / edit to a field of the table 83 | editRelations, 84 | deleteField, 85 | tables 86 | }) { 87 | 88 | // these constants represent each option in the dialog-box of the 'Add Table' button 89 | const { 90 | defaultValue, // 'Dafault Value' 91 | fieldNum, // '' 92 | name, // 'Field Name' 93 | primaryKey, // 'Primary Key' 94 | queryable, // 'Queryable' 95 | relation, // '' 96 | relationSelected, // '' 97 | required, // 'Required' 98 | tableNum, // '' 99 | type, // 'Type of Relationship' 100 | unique, // 'Unique' 101 | } = field; 102 | 103 | const { refType } = relation; 104 | let relationTableIdx = relation.tableIndex; 105 | let relationFieldIdx = relation.fieldIndex; 106 | 107 | function isChecked(id, field) { 108 | // represents the switch button to be affected 109 | const selectedSwitch = document.querySelector(id); 110 | // if 'field' exists, simulate a 'click' event on the switch button represented by 'selectedSwitch' 111 | if (field) selectedSwitch.click(); 112 | } 113 | 114 | /* 115 | handles 'checking' the swtich buttons associated with 'Primary Key', 'Unique', 'Required', and 116 | 'Queryable' 117 | */ 118 | useEffect(() => { 119 | // isChecked() is defined on line 106 120 | isChecked("#primaryKey" + fieldNum, primaryKey); 121 | isChecked("#unique" + fieldNum, unique); 122 | isChecked("#required" + fieldNum, required); 123 | isChecked("#queryable" + fieldNum, queryable); 124 | }, []); 125 | 126 | const relationalTableNames = []; 127 | const populateTableRelationOptions = () => { 128 | for (let key in tables) { 129 | relationalTableNames.push(); 130 | } 131 | } 132 | populateTableRelationOptions(); 133 | 134 | const relationalFieldsNames = []; 135 | const populateFieldRelationOptions = (relTblIdx) => { 136 | if (relTblIdx !== -1) { 137 | for (let field in tables[relTblIdx].fields) { 138 | relationalFieldsNames.push(); 139 | } 140 | } 141 | } 142 | populateFieldRelationOptions(relationTableIdx); 143 | 144 | // these components represent buttons, input fields, etc. associated with each option 145 | return ( 146 | 147 | 148 | deleteField(fieldIndex)}> 149 | 150 | 151 | 152 | 153 | 154 | editField({ value: e.target.value, fieldKey: fieldIndex, fieldProperty: "name" })} 160 | /> 161 | 162 | 163 | 164 | editField({ value: e.target.value, fieldKey: fieldIndex, fieldProperty: "type" })} 167 | > 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | editField({ value: e.target.value, fieldKey: fieldIndex, fieldProperty: "defaultValue" })} 182 | /> 183 | 184 | 185 | 186 | 198 | 199 | 200 | 201 | 213 | 214 | 215 | 216 | 228 | 229 | 230 | 231 | 243 | 244 | 245 | {/* relationship to table */} 246 | 247 | { 251 | if (e.target.value === "") { 252 | // relationTableIdx = -1; 253 | // relationFieldIdx = -1; 254 | editRelations({ 255 | relationValue: -1, 256 | relationFieldKey: fieldIndex, 257 | relationFieldProperty: "tableIndex" 258 | }) 259 | } 260 | else 261 | editRelations({ 262 | relationValue: e.target.value, 263 | relationFieldKey: fieldIndex, 264 | relationFieldProperty: "tableIndex" 265 | }) 266 | }} 267 | > 268 | 269 | {relationalTableNames} 270 | 271 | 272 | 273 | {/* relationship to table fields */} 274 | 275 | { 281 | editRelations({ 282 | relationValue: e.target.value, 283 | relationFieldKey: fieldIndex, 284 | relationFieldProperty: "fieldIndex" 285 | }) 286 | } 287 | } 288 | > 289 | 290 | {relationalFieldsNames} 291 | 292 | 293 | 294 | 295 | editRelations({ 301 | relationValue: e.target.value, 302 | relationFieldKey: fieldIndex, 303 | relationFieldProperty: "refType" 304 | }) 305 | } 306 | > 307 | {relationTableIdx !== -1 && } 308 | {relationTableIdx !== -1 && } 309 | {relationTableIdx !== -1 && } 310 | {relationTableIdx !== -1 && } 311 | 312 | 313 | 314 | 315 | ) 316 | } 317 | 318 | export default TableInput; -------------------------------------------------------------------------------- /src/components/popup/tableNameInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | /*-------------------- Styled Components --------------------*/ 5 | 6 | // styles the space around the border of the DOM elmement containing the text 'Enter Table Name' 7 | const Wrapper = styled.div` 8 | background-color: white; 9 | `; 10 | 11 | // styles the input text ('Enter Table Name') 12 | const Input = styled.input` 13 | padding: 5px 10px; 14 | width: calc(100% - 22px); 15 | color: #dd399c; 16 | font-weight: 400; 17 | font-size: 1em; 18 | border: none; 19 | margin: 1px; 20 | margin-top: 4px; 21 | letter-spacing: 0.1em; 22 | ::placeholder { 23 | color: #dd399c; 24 | opacity: 0.75; 25 | } 26 | `; 27 | 28 | /*-------------------- Functional Component --------------------*/ 29 | 30 | function TableNameInput({ name, editTableName }) { 31 | 32 | // handles changes to the input field that represents the name of the new table 33 | const onTableNameChange = (e) => editTableName(e.target.value); 34 | 35 | return ( 36 | 37 | 45 | 46 | ) 47 | } 48 | 49 | export default TableNameInput; 50 | -------------------------------------------------------------------------------- /src/components/popup/welcome.jsx: -------------------------------------------------------------------------------- 1 | // This is also the import button in the header 2 | /* eslint-disable no-unused-vars */ 3 | import React, { useContext, useState } from "react"; 4 | import { 5 | Button, 6 | Dialog, 7 | DialogActions, 8 | DialogContent, 9 | DialogContentText, 10 | DialogTitle, 11 | Input, 12 | Paper 13 | } from "@material-ui/core"; 14 | import Draggable from "react-draggable"; 15 | import { styled } from "@material-ui/styles"; 16 | import { Store } from '../../state/store'; 17 | import { SET_POP_UP, IMPORT_TABLES } from '../../actions/actionTypes'; 18 | import buildENV from '../../utils/buildENV'; 19 | const electron = window.require('electron'); 20 | const ipc = electron.ipcRenderer; 21 | // import { build } from "protobufjs"; 22 | /*-------------------- Styled components --------------------*/ 23 | // styles the header of the dialog-box that appears when the application is first loaded 24 | const Title = styled(DialogTitle)({ 25 | width: "500px", 26 | textAlign: "center", 27 | padding: '25px', 28 | color: 'white', 29 | background: '#161e26', 30 | }); 31 | // styles the GraphQL logo of the dialog-box that appears when the application is first loaded 32 | const Text = styled(DialogContentText)({ 33 | color: "white", 34 | height: 'auto', 35 | width: '215px', 36 | margin: '15px', 37 | textAlign: "center", 38 | marginBottom: '7px', 39 | }); 40 | // styles the definitions of the dialog-box that appears when the application is first loaded 41 | const ContentDiv = styled(DialogContent)({ 42 | display: 'flex', 43 | justifyContent: 'center', 44 | textAlign: 'center', 45 | width: '500px', 46 | color: '#161e26', 47 | }); 48 | // styles the start button of the dialog-box that appears when the application is first loaded 49 | const StartButton = styled(Button)({ 50 | width: '200px', 51 | border: '1px solid #161e26', 52 | color: '#161e26', 53 | padding: '10px', 54 | marginTop: '0px', 55 | marginBottom: '10px' 56 | }); 57 | // styles the postgres database input field displayed after the import tables button is clicked 58 | const DBinput = styled(Input)({ 59 | lineHeight: '1.75px', 60 | borderRadius: '5px', 61 | margin: '0 6px 0 0', 62 | width: '300px', 63 | marginBottom: '10px', 64 | padding: '10px', 65 | color: '#dd399c', 66 | fontWeight: '400', 67 | border: '1px solid black', 68 | letterSpacing: '0.1em', 69 | }) 70 | // styles the submit button for the postgres database input field 71 | const Submit = styled(Button)({ 72 | width: '100px', 73 | border: '1px solid #161e26', 74 | color: 'white', 75 | backgroundColor: 'rgba(221, 57, 156, 1)', 76 | padding: '10px', 77 | marginTop: '0', 78 | marginBottom: '10px', 79 | }) 80 | /* 81 | styles the space around the start button of the dialog-box that appears when the 82 | application is first loaded 83 | */ 84 | const DialogActionsDiv = styled(DialogActions)({ 85 | justifyContent: 'center', 86 | margin: 0 87 | }); 88 | /*-------------------- Functional Component --------------------*/ 89 | function PaperComponent(props) { 90 | return ( 91 | 92 | 93 | 94 | ); 95 | } 96 | function DraggableDialog(props) { 97 | // USE CONTEXT 98 | const { state: { popUp }, dispatch } = useContext(Store); 99 | const handleClose = () => { 100 | dispatch({ type: SET_POP_UP, payload: '' }); 101 | } 102 | const keyUpToHandleClose = (e) => { 103 | // condition that handles the 'Escape' and 'Enter' buttons on a keyboard 104 | if (e.keyCode == 13 || e.keyCode == 27) { 105 | /* 106 | invoke 'dispatch' when 'Escape' or 'Enter' is pressed and pass the object type of 107 | 'SET_POP_UP' and a payload of '' 108 | */ 109 | dispatch({ type: SET_POP_UP, payload: '' }); 110 | } 111 | } 112 | // END OF USE CONTEXT 113 | // USE STATE to set visibility of postgres URI input field 114 | let [show, setShow] = useState({ display: "none" }); 115 | // END OF USE STATE 116 | const setURI = (e) => { 117 | e.preventDefault(); 118 | const URI = document.getElementById('dbInput').value; 119 | if (URI.slice(0, 11).toLowerCase() === 'postgres://' || URI.slice(0, 13).toLowerCase() === 'postgresql://') { 120 | // emitting message to electron window to open save dialog 121 | ipc.send('create-env-file', buildENV(URI)); 122 | function importTables() { 123 | const tables = {}; 124 | ipc.on('tables-imported', (event, arg) => { 125 | console.log("import tables, no async: ", arg) 126 | dispatch({ type: IMPORT_TABLES, payload: arg}) 127 | dispatch({ type: SET_POP_UP, payload: '' }) 128 | }) 129 | ipc.send('import-tables', tables); 130 | } 131 | //added to force table import to wait on env file creation 132 | ipc.on('env-file-created', importTables); 133 | } else { 134 | console.log('That is not a valid input'); 135 | // document.querySelector('#error').classList.remove('invisible') 136 | } 137 | } 138 | return ( 139 |
140 | 141 | P R O T O G R A P H Q L 142 | 143 | 144 | graphQLLogo 149 | 150 | 151 |
    152 |
  1. ◊ Add Or Import Tables
  2. 153 |
  3. ◊ Build Your Schema
  4. 154 |
  5. ◊ Generate Your Code
  6. 155 |
  7. ◊ Visualize Your Schema
  8. 156 |
  9. ◊ Create Your Apollo Server
  10. 157 |
  11. ◊ Test Your Queries
  12. 158 |
  13. ◊ Export Your Tests
  14. 159 |
160 | 161 |
162 | 163 | Create Your Tables 164 | setShow({ display: 'block' })} color="primary" >Import Tables 165 | 166 |
167 | 168 | 169 | Connect 170 | 171 |
172 |
173 |
174 | ); 175 | } 176 | export default DraggableDialog; -------------------------------------------------------------------------------- /src/components/sideBar/navButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { keyframes } from 'styled-components'; 3 | 4 | /*-------------------- Styled Components --------------------*/ 5 | 6 | // styles the 'Schema', 'Code', and 'Visualize' buttons 7 | const fontColor = keyframes` 8 | to { 9 | color: #e535ab; 10 | box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.10); 11 | } 12 | `; 13 | 14 | // styles the 'Schema', 'Code', and 'Visualize' buttons 15 | const ButtonContainer = styled.div` 16 | padding: 1px; 17 | border-bottom: 1px solid rgba(0, 0, 0, 0.08); 18 | min-width: 200px; 19 | cursor: pointer; 20 | &:hover { 21 | animation: ${fontColor} .5s; 22 | animation-fill-mode: both; 23 | }; 24 | `; 25 | 26 | // styles the 'Schema', 'Code', and 'Visualize' buttons 27 | const Button = styled.div` 28 | height: 60px; 29 | background-color: none; 30 | margin: auto; 31 | margin-left: calc(15.5px + .25vw); 32 | margin-top: 25px; 33 | `; 34 | 35 | // styles the 'Schema', 'Code', and 'Visualize' buttons 36 | const Icon = styled.span` 37 | margin: 5px; 38 | font-size: calc(14px + 1vw); 39 | `; 40 | 41 | /*-------------------- Functional Component --------------------*/ 42 | 43 | const NavButton = ({ className, click, view, style }) => { 44 | return ( 45 | 46 | 50 | 51 | ) 52 | } 53 | 54 | export default NavButton; 55 | -------------------------------------------------------------------------------- /src/components/sideBar/navSidebar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import NavButton from './navButton'; 3 | import NoTableButton from './noTableButton'; 4 | import styled from 'styled-components'; 5 | import { Store } from '../../state/store'; 6 | import { 7 | SET_POP_UP, 8 | SET_VIEW, 9 | ADD_TABLE, 10 | } from '../../actions/actionTypes'; 11 | 12 | /*-------------------- Styled Components --------------------*/ 13 | 14 | // styles the left side-bar of the application 15 | const SideBar = styled.div` 16 | background-color: white; 17 | grid-area: navSideBar; 18 | border-right: 1px solid rgba(0, 0, 0, 0.12); 19 | box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.10); 20 | display: inline-block; 21 | height: calc(100vh - 64px); 22 | position: relative; 23 | `; 24 | 25 | /*-------------------- Functional Component --------------------*/ 26 | 27 | const views = ["Schema", "Code", "Visualize", "Tests"] 28 | 29 | // alters the clicked button of the side-bar menu 30 | function changeButtonStyleOnClick(view) { 31 | // selects the button that was clicked 32 | const currentButton = document.querySelector(`#${view}`); 33 | // sets the box shadow of the clicked button 34 | currentButton.style.boxShadow = "4px 4px 4px rgba(0, 0, 0, 0.10)"; 35 | // styles the border type of the clicked button 36 | currentButton.style.border = "2px solid rgba(0, 0, 0, 0.12)"; 37 | // styles the right border of the clicked button 38 | currentButton.style.borderRight = "4px solid rgba(221, 57, 156, 1)"; 39 | // iterate through all the buttons ('Schema', 'Code', and 'Visualize') 40 | for (let j = 0; j < views.length; j++) { 41 | /* 42 | if the current button in the iteration is not the button that was clicked... go to line 46 43 | */ 44 | if (view !== views[j]) { 45 | // select the current button in the iteration 46 | const button = document.querySelector(`#${views[j]}`); 47 | // set the box shadow of the current button in the iteration 48 | button.style.boxShadow = "1px 2px 3px rgba(0, 0, 0, 0.10)"; 49 | // set the border type of the current button in the iteration 50 | button.style.border = "none"; 51 | // set the right border style of the current button in the iteration 52 | button.style.borderRight = "1px solid rgba(0, 0, 0, 0.12)"; 53 | } 54 | } 55 | } 56 | 57 | function NavSideBar() { 58 | /* 59 | -> connects the application to the context (utilized by Hooks in React) and facilitates the ability to 60 | update the context of the application 61 | -> the context is initialized by useContext() and specified by Store which is found 62 | in /components/state/store.jsx 63 | */ 64 | const { dispatch, state: { tableIndex } } = useContext(Store); 65 | return ( 66 | 67 | { 72 | dispatch({ type: SET_VIEW, payload: 'schema' }) 73 | dispatch({ type: SET_POP_UP, payload: '' }) 74 | changeButtonStyleOnClick("Schema") 75 | document.querySelector("svg") ? document.querySelector("svg").remove() : ""; 76 | }} 77 | /> 78 | { 83 | dispatch({ type: SET_VIEW, payload: 'code' }) 84 | dispatch({ type: SET_POP_UP, payload: '' }) 85 | changeButtonStyleOnClick("Code") 86 | document.querySelector("svg") ? document.querySelector("svg").remove() : ""; 87 | }} 88 | /> 89 | { 94 | dispatch({ type: SET_VIEW, payload: 'visualize' }) 95 | dispatch({ type: SET_POP_UP, payload: '' }) 96 | changeButtonStyleOnClick("Visualize") 97 | document.querySelector("svg") ? document.querySelector("svg").remove() : ""; 98 | }} 99 | /> 100 | { 105 | dispatch({ type: SET_VIEW, payload: 'tests' }) 106 | dispatch({ type: SET_POP_UP, payload: '' }) 107 | changeButtonStyleOnClick("Tests") 108 | document.querySelector("svg") ? document.querySelector("svg").remove() : ""; 109 | }} 110 | /> 111 | {tableIndex !== 0 && { 116 | dispatch({ type: SET_VIEW, payload: 'schema' }) 117 | dispatch({ type: SET_POP_UP, payload: 'table' }) 118 | dispatch({ type: ADD_TABLE }) 119 | changeButtonStyleOnClick("Schema") 120 | document.querySelector("svg") ? document.querySelector("svg").remove() : ""; 121 | }} 122 | style={{ 123 | position: 'absolute', 124 | bottom: 0, 125 | borderTop: '1px solid rgba(0, 0, 0, 0.08)', 126 | width: '100%' 127 | }} 128 | />} 129 | {tableIndex === 0 && { 134 | dispatch({ type: SET_VIEW, payload: 'schema' }) 135 | dispatch({ type: SET_POP_UP, payload: 'table' }) 136 | dispatch({ type: ADD_TABLE }) 137 | changeButtonStyleOnClick("Schema") 138 | document.querySelector("svg") ? document.querySelector("svg").remove() : ""; 139 | }} 140 | style={{ 141 | position: 'absolute', 142 | bottom: 0, 143 | borderTop: '1px solid rgba(0, 0, 0, 0.08)', 144 | width: '100%' 145 | }} 146 | />} 147 | 148 | ) 149 | } 150 | 151 | export default NavSideBar; 152 | -------------------------------------------------------------------------------- /src/components/sideBar/noTableButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { keyframes } from 'styled-components'; 3 | 4 | /*-------------------- Styled Components --------------------*/ 5 | 6 | // styles animation of the 'Add Table' button 7 | const fontColor = keyframes` 8 | 0% { 9 | color: rgba(50, 67, 83, 1); 10 | box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.00); 11 | bottom: 0px; 12 | } 13 | 50% { 14 | color: #e535ab; 15 | box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.10); 16 | bottom: 25px; 17 | } 18 | 100% { 19 | color: rgba(50, 67, 83, 1); 20 | box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.00); 21 | bottom: 0px; 22 | } 23 | `; 24 | 25 | // styles the 'Add Table' button and surrounding space 26 | const ButtonContainer = styled.div` 27 | padding: 1px; 28 | border-bottom: 1px solid rgba(0, 0, 0, 0.08); 29 | min-width: 200px; 30 | cursor: pointer; 31 | animation: ${fontColor} 2s; 32 | animation-fill-mode: both; 33 | animation-iteration-count: 20; 34 | `; 35 | 36 | // styles the 'Add Table' button and surrounding space 37 | const Button = styled.div` 38 | height: 60px; 39 | background-color: none; 40 | margin: auto; 41 | margin-left: calc(15.5px + .25vw); 42 | margin-top: 25px; 43 | `; 44 | 45 | // styles the icon associated with the 'Add Table' button 46 | const Icon = styled.span` 47 | margin: 5px; 48 | font-size: calc(14px + 1vw); 49 | `; 50 | 51 | /*-------------------- Functional Component --------------------*/ 52 | 53 | const NoTableButton = ({ className, click, view, style }) => { 54 | return ( 55 | 56 | 60 | 61 | ) 62 | } 63 | 64 | export default NoTableButton; 65 | -------------------------------------------------------------------------------- /src/components/sideBar/visualizerSidebar.jsx: -------------------------------------------------------------------------------- 1 | // This file styles the contents associated with the 'Visualize' button 2 | 3 | import React, { useContext } from 'react'; 4 | import styled from 'styled-components'; 5 | import { Store } from '../../state/store'; 6 | import { queryTypeCreator } from '../../utils/buildVisualizerJson' 7 | import VizType from './vizType'; 8 | import deepClone from '../../utils/deepClone' 9 | 10 | /*-------------------- Styled Components --------------------*/ 11 | 12 | // styles the divider between the visualization area and the right side-bar 13 | const SideBar = styled.div` 14 | grid-area: bar; 15 | box-shadow: -2px 2px 3px rgba(0, 0, 0, 0.10); 16 | background: white; 17 | font-family: "Roboto", sans-serif; 18 | // overflow: scroll; 19 | `; 20 | 21 | // styles the title of the right side-bar that appears after clicking the 'Visualize' button 22 | const Header = styled.p` 23 | font-size: 18px; 24 | padding: 8px; 25 | font-weight: 500; 26 | text-align: center; 27 | border-bottom: 1px solid rgba(0, 0, 0, 0.08); 28 | ` 29 | 30 | const TypeContainer = styled.div` 31 | height: calc(100vh - 189px) 32 | border-bottom: 1px solid rgba(0, 0, 0, 0.08); 33 | ` 34 | 35 | // styles the title 'Color Legend' of the right side-bar that appears after clicking the 'Visualize' button 36 | const ColorLegend = styled.div` 37 | position: absolute; 38 | bottom: 0; 39 | padding-top: 10px; 40 | height: 115px; 41 | text-align: center; 42 | font-size: 18px; 43 | font-weight: 500; 44 | background-color: white; 45 | z-index: 2; 46 | ` 47 | 48 | // styles the container that contains the 'Queries', 'Types', and 'Queryable Fields' text fields 49 | const ColorContainer = styled.div` 50 | display: flex; 51 | width: 100%; 52 | height: 20px; 53 | ` 54 | 55 | // styles the 'Queries' text 56 | const First = styled.div` 57 | text-align: left; 58 | vertical-align: middle; 59 | margin-left: 20px; 60 | background-color: #A852E5; 61 | width: 30px; 62 | height: 15px; 63 | ` 64 | 65 | // styles the 'Types' text 66 | const Second = styled.div` 67 | text-align: left; 68 | vertical-align: middle; 69 | margin-left: 20px; 70 | background-color: #14BDEB; 71 | width: 30px; 72 | height: 15px; 73 | ` 74 | 75 | // styles the 'Queryable Fields' text 76 | const Third = styled.div` 77 | text-align: left; 78 | vertical-align: middle; 79 | margin-left: 20px; 80 | background-color: #0D18E8; 81 | width: 30px; 82 | height: 15px; 83 | ` 84 | 85 | // styles the 'Queries', 'Types', and 'Queryable Fields' text fields 86 | const Text = styled.span` 87 | padding-left: 10px; 88 | font-size: 14px; 89 | font-weight: 300; 90 | color:none; 91 | ` 92 | 93 | /*-------------------- Functional Component --------------------*/ 94 | 95 | 96 | function VisualizerSideBar() { 97 | /* 98 | -> connects the application to the context (utilized by Hooks in React) and facilitates the ability to 99 | update the context of the application 100 | -> the context is initialized by useContext() and specified by Store which is found 101 | in /components/state/store.jsx 102 | */ 103 | const { 104 | state: { 105 | tables 106 | } 107 | } = useContext(Store); 108 | 109 | 110 | // generates a deep clone of 'tables' which is defined on line 105 111 | const queryTypes = queryTypeCreator(deepClone(tables)) 112 | const vizTypes = [] 113 | for (let i in queryTypes) { 114 | /* 115 | append each respective cell to the content under the 'Types' title that appears in the right side-bar after clicking 116 | the 'Visualize' button 117 | */ 118 | vizTypes.push() 119 | } 120 | 121 | return ( 122 | 123 | 124 |
Types
125 | {vizTypes} 126 |
127 | 128 | Color Legend 129 | 130 | 131 | Queries 132 | 133 | 134 | 135 | Types 136 | 137 | 138 | 139 | Queryable Fields 140 | 141 | 142 |
143 | ) 144 | } 145 | 146 | export default VisualizerSideBar; 147 | -------------------------------------------------------------------------------- /src/components/sideBar/vizType.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import styled, { keyframes } from 'styled-components'; 3 | 4 | /* 5 | styles the content associated with the title 'Types' that appears in the right side-bar after 6 | clicking the 'Visualize' button 7 | */ 8 | const fontColor = keyframes` 9 | to { 10 | color: #e535ab; 11 | box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.10); 12 | } 13 | `; 14 | 15 | /* 16 | styles the content associated with the title 'Types' that appears in the right side-bar after 17 | clicking the 'Visualize' button 18 | */ 19 | const ButtonContainer = styled.div` 20 | font-size: 14px; 21 | padding-top: 20px; 22 | padding-bottom: 20px; 23 | border-bottom: 1px solid rgba(0, 0, 0, 0.08); 24 | min-width: 200px; 25 | &:hover { 26 | animation: ${fontColor} .5s; 27 | animation-fill-mode: both; 28 | }; 29 | `; 30 | 31 | /* 32 | styles the title of each type under the 'Types' title that appears in the right side-bar after clicking 33 | the 'Visualize' button 34 | */ 35 | const TypeName = styled.div` 36 | margin-left: 20px; 37 | text-decoration: underline; 38 | font-weight: 450; 39 | text-align: left; 40 | padding-bottom: 20px; 41 | `; 42 | 43 | /* 44 | styles the field of each type under the 'Types' title that appears in the right side-bar after clicking 45 | the 'Visualize' button 46 | */ 47 | const Field = styled.div` 48 | margin-left: 20px; 49 | padding-bottom: 10px; 50 | `; 51 | 52 | 53 | function VizType ({ table }) { 54 | const values = [] 55 | // iterate all fields in the table 56 | for (let key in table.fields) { 57 | /* 58 | append the respective field to the respective type cell under the 'Types' title that appears in the 59 | right side-bar after clicking the 'Visualize' button 60 | */ 61 | values.push({key}: {table.fields[key]}) 62 | } 63 | return ( 64 | 65 | {table.name} 66 | {values} 67 | 68 | ) 69 | } 70 | 71 | export default VizType -------------------------------------------------------------------------------- /src/components/view/codeView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Store } from '../../state/store'; 3 | import styled from 'styled-components'; 4 | 5 | /*-------------------- Styled Components --------------------*/ 6 | 7 | /* 8 | styles the surrounding space that exists around the code display areas of 'GraphQL Schema', 'SQL Scripts', 9 | and 'GraphQL Resolvers' that appears after clicking the 'Code' button 10 | */ 11 | const Code = styled.div` 12 | margin: 13px; 13 | font-family: Courier New, Consolas, Monaco, Lucida Console; 14 | font-size: 15px; 15 | background-color: #EFF0F1; 16 | display: grid; 17 | height: calc(100vh - 26px - 64px); 18 | `; 19 | 20 | /* 21 | styles the background of 'GraphQL Schema', 'SQL Scripts', and 'GraphQL Resolvers' that appears after 22 | clicking the 'Code' button 23 | */ 24 | const Column = styled.div` 25 | background-color: white; 26 | margin: 10px; 27 | padding: 10px; 28 | height: auto; 29 | overflow: scroll; 30 | border: 1px solid rgba(0, 0, 0, 0.2); 31 | box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.12); 32 | ::-webkit-scrollbar { 33 | -webkit-appearance: none; 34 | width: 10px; 35 | height: 10px; 36 | }; 37 | ::-webkit-scrollbar-thumb { 38 | border-radius: 5px; 39 | background-color: rgba(0, 0, 0, 0.12); 40 | } 41 | `; 42 | 43 | /* 44 | styles the titles associated with 'GraphQL Schema', 'SQL Scripts', and 'GraphQL Resolvers' that appears 45 | after clicking the 'Code' button 46 | */ 47 | const Title = styled.p` 48 | font-size: 20px; 49 | color: rgba(221, 57, 156, 1); 50 | font-family: "Roboto", sans-serif; 51 | padding-bottom: 15px; 52 | `; 53 | 54 | /*-------------------- Functional Component --------------------*/ 55 | 56 | function CodeView() { 57 | /* 58 | -> connects the application to the context (utilized by Hooks in React) and facilitates the ability to 59 | update the context of the application 60 | -> the context is initialized by useContext() and specified by Store which is found 61 | in /components/state/store.jsx 62 | */ 63 | const { state: { gqlSchema, gqlResolvers, sqlScripts } } = useContext(Store); 64 | 65 | return ( 66 | 67 | 68 | GraphQL Schema 69 |
{gqlSchema}
70 |
71 | 72 | SQL Scripts 73 |
{sqlScripts}
74 |
75 | 76 | GraphQL Resolvers 77 |
{gqlResolvers}
78 |
79 |
80 | ); 81 | } 82 | 83 | export default CodeView; 84 | -------------------------------------------------------------------------------- /src/components/view/mainView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import SchemaView from '../../components/view/schemaView'; 3 | import CodeView from '../../components/view/codeView'; 4 | import VisualizeView from '../../components/view/visualizeView'; 5 | // import GraphiqlView from './graphiqlView'; // removes unused comoponent 6 | import TableForm from '../popup/tableForm'; 7 | import { Store } from '../../state/store'; 8 | import TestsView from './testsView'; 9 | 10 | function MainView() { 11 | /* 12 | -> connects the application to the context (utilized by Hooks in React) and facilitates the ability to 13 | update the context of the application 14 | -> the context is initialized by useContext() and specified by Store which is found 15 | in /components/state/store.jsx 16 | */ 17 | const { state: { view, popUp } } = useContext(Store); 18 | return ( 19 |
20 | {view === 'code' && } 21 | {view === 'schema' && } 22 | {view === 'visualize' && } 23 | {view === 'tests' && } 24 | {popUp === 'table' && } 25 |
26 | ) 27 | } 28 | 29 | export default MainView; 30 | -------------------------------------------------------------------------------- /src/components/view/schemaTable.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withStyles, makeStyles } from '@material-ui/core/styles'; 3 | import { 4 | Table, 5 | Typography, 6 | TableBody, 7 | TableCell, 8 | TableHead, 9 | TableRow, 10 | Paper 11 | } from '@material-ui/core/'; 12 | import styled from 'styled-components'; 13 | 14 | /*-------------------- Styled Components --------------------*/ 15 | 16 | // styles the 'Name' section of each cell that appears after clicking the 'Schema' button 17 | const StyledTableCell = withStyles(theme => ({ 18 | head: { 19 | backgroundColor: '#324353', 20 | color: theme.palette.common.white, 21 | fontSize: 16, 22 | size: 'small', 23 | color: 'white' 24 | }, 25 | body: { 26 | color: '#222', 27 | fontSize: 15, 28 | }, 29 | }))(TableCell); 30 | 31 | // styles each field of each cell that appears after clicking the 'Schema' button 32 | const StyledTableRow = withStyles(theme => ({ 33 | root: { 34 | '&:nth-of-type(odd)': { 35 | backgroundColor: theme.palette.background.default, 36 | }, 37 | }, 38 | }))(TableRow); 39 | 40 | 41 | const useStyles = makeStyles(theme => ({ 42 | // styles each cell that appears after clicking the 'Schema' button 43 | root: { 44 | width: 250, 45 | height: '100%', 46 | }, 47 | // styles each cell that appears after clicking the 'Schema' button 48 | table: { 49 | minWidth: 200 50 | }, 51 | // styles the title of each cell that appears after clicking the 'Schema' button 52 | title: { 53 | variant: "h6", 54 | id: "tableTitle", 55 | minWidth: 200, 56 | paddingTop: '3px', 57 | paddingLeft: '16px', 58 | paddingRight: '7px', 59 | paddingBottom: '2px', 60 | color: '#dd399c', 61 | fontWeight: 400, 62 | letterSpacing: '0.1em', 63 | } 64 | })); 65 | 66 | // styles the icons of the title section of each cell that appears after clicking the 'Schema' button 67 | const Buttons = styled.span` 68 | color: black; 69 | margin-left: 5px; 70 | cursor: pointer; 71 | &:hover{ 72 | color: #DD399C; 73 | } 74 | `; 75 | 76 | /*-------------------- Functional Component --------------------*/ 77 | 78 | function SchemaTable({ 79 | table, 80 | setPopUp, 81 | tableKey, 82 | deleteTable, 83 | editTable 84 | }) { 85 | // sets the classes to be used in each cell, as defined on line 41 86 | const classes = useStyles(); 87 | const fields = ( 88 | Object.keys(table.fields).map(fieldKey => ( 89 | { 90 | // populates, for each cell, the table name, field name, field type, etc. 91 | name: table.fields[fieldKey].name, 92 | type: table.fields[fieldKey].type, 93 | tableNum: table.fields[fieldKey].tableNum, 94 | fieldNum: table.fields[fieldKey].fieldNum 95 | } 96 | )) 97 | ) 98 | 99 | return ( 100 | 101 | 102 | {table.type} 103 | 104 | 105 | deleteTable(tableKey)} /> 106 | 107 | 108 | 109 | 110 | { 113 | editTable(tableKey); 114 | setPopUp('table'); 115 | }} 116 | /> 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Name 125 | Type 126 | 127 | 128 | 129 | 130 | {fields.map(field => ( 131 | 132 | 133 | {field.name} 134 | 135 | 136 | {field.type} 137 | 138 | 139 | ))} 140 | 141 | 142 |
143 |
144 | ); 145 | } 146 | 147 | export default SchemaTable; 148 | -------------------------------------------------------------------------------- /src/components/view/schemaView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import SchemaTable from './schemaTable'; 3 | import styled from 'styled-components'; 4 | import { Store } from '../../state/store'; 5 | import { 6 | SET_POP_UP, 7 | SET_VIEW, 8 | EDIT_TABLE, 9 | DELETE_TABLE, 10 | HIDE_DISPLAY_ERROR, 11 | THROTTLE_DISPLAY_ERROR, 12 | } from '../../actions/actionTypes'; 13 | 14 | /*-------------------- Styled Component --------------------*/ 15 | 16 | const View = styled.div` 17 | display: flex; 18 | flex-wrap: wrap; 19 | justify-content: flex-start; 20 | `; 21 | 22 | /*-------------------- Functional Component --------------------*/ 23 | 24 | function SchemaView() { 25 | /* 26 | -> connects the application to the context (utilized by Hooks in React) and facilitates the ability to 27 | update the context of the application 28 | -> the context is initialized by useContext() and specified by Store which is found 29 | in /components/state/store.jsx 30 | */ 31 | const { dispatch, state: { tables, displayError } } = useContext(Store); 32 | 33 | const tablesArray = Object.keys(tables).map(tableKey => ( 34 | dispatch({ type: SET_POP_UP, payload })} 40 | setView={payload => dispatch({ type: SET_VIEW, payload })} 41 | style={{ margin: "10px" }} 42 | deleteTable={payload => dispatch({ type: DELETE_TABLE, payload })} 43 | editTable={payload => dispatch({ type: EDIT_TABLE, payload })} 44 | /> 45 | )) 46 | 47 | // dispatch hide error only once per lifecycle of displayError.displayStatus = true 48 | if (displayError.displayStatus && displayError.throttleStatus) { 49 | dispatch({ type: THROTTLE_DISPLAY_ERROR }); 50 | setTimeout(() => { 51 | dispatch({ type: HIDE_DISPLAY_ERROR }); 52 | dispatch({ type: THROTTLE_DISPLAY_ERROR }); 53 | }, 3250); 54 | } 55 | 56 | return ( 57 | 58 | {tablesArray} 59 | {displayError.displayStatus && 60 |
61 | Please remove relationship from
62 | '{displayError.relatedField}' field in '{displayError.relatedTable}' table first. 63 |
} 64 |
65 | ) 66 | } 67 | 68 | export default SchemaView; 69 | -------------------------------------------------------------------------------- /src/components/view/testsView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from 'react'; 2 | import styled from 'styled-components'; 3 | import { Store } from '../../state/store'; 4 | import { UPDATE_QUERIES, ADD_APOLLO_SERVER_URI } from '../../actions/actionTypes'; 5 | 6 | import { ApolloClient } from 'apollo-client'; 7 | import { InMemoryCache } from 'apollo-cache-inmemory'; 8 | import { SET_POP_UP } from '../../actions/actionTypes'; 9 | import { onError } from 'apollo-link-error'; 10 | import { ApolloLink } from 'apollo-link'; 11 | import { HttpLink, gql } from 'apollo-boost' 12 | // import { flattenDiagnosticMessageText } from 'typescript'; 13 | 14 | const electron = window.require('electron'); 15 | const ipc = electron.ipcRenderer; 16 | 17 | /*-------------------- Styled Components --------------------*/ 18 | 19 | // styles the application window 20 | const Code = styled.div` 21 | margin: 13px; 22 | font-family: Courier New, Consolas, Monaco, Lucida Console; 23 | font-size: 15px; 24 | background-color: #EFF0F1; 25 | display: grid; 26 | height: calc(100vh - 26px - 64px); 27 | `; 28 | 29 | // styles each white card 30 | const Column = styled.div` 31 | background-color: white; 32 | margin: 10px; 33 | padding: 10px; 34 | height: auto; 35 | overflow: scroll; 36 | border: 1px solid rgba(0, 0, 0, 0.2); 37 | box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.12); 38 | position: relative; 39 | ::-webkit-scrollbar { 40 | -webkit-appearance: none; 41 | width: 10px; 42 | height: 10px; 43 | }; 44 | ::-webkit-scrollbar-thumb { 45 | border-radius: 5px; 46 | background-color: rgba(0, 0, 0, 0.12); 47 | } 48 | `; 49 | 50 | // styles the titles 51 | const Title = styled.p` 52 | font-size: 20px; 53 | color: rgba(221, 57, 156, 1); 54 | font-family: "Roboto", sans-serif; 55 | padding-bottom: 15px; 56 | `; 57 | 58 | // base style for button components 59 | const Button = styled.button` 60 | height: auto; 61 | font-size: .85em; 62 | font-weight: 300; 63 | margin: 8px; 64 | margin-right: 10px; 65 | margin-left: 10px; 66 | padding: 10px 0px; 67 | border-radius: 5px; 68 | box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.10); 69 | background-color: rgba(50, 67, 83, 0.85); 70 | color: white; 71 | width: 48%; 72 | text-align: center; 73 | &:hover { 74 | background-color: #DD399C; 75 | cursor: pointer; 76 | } 77 | @media only screen and (max-width: 1354px) { 78 | width: 100%; 79 | } 80 | `; 81 | 82 | // styles uri input 83 | const Input = styled.input` 84 | height: 2.5em; 85 | border-radius: 5px; 86 | margin: 0; 87 | width: 75%; 88 | margin: 2.5em auto; 89 | font-size: .75em; 90 | `; 91 | 92 | // styles textboxes in left column 93 | const Textbox = styled.textarea` 94 | height: 20em; 95 | border-radius: 5px; 96 | margin: 0; 97 | width: 98%; 98 | margin: 5px auto; 99 | font-size: .75em; 100 | `; 101 | 102 | /*-------------------- Functional Component --------------------*/ 103 | // const { dispatch, state: { queries, apolloServerURI } } = useContext(Store); 104 | 105 | // tests user input against endpoint 106 | function TestsView() { 107 | const { dispatch, state: { queries, apolloServerURI } } = useContext(Store); 108 | 109 | //variable to store response if client query returns an error 110 | let r; 111 | 112 | //creates ApolloClient link to apolloServerURI from user, or localhost:3000/GraphQL if none is provided 113 | const client = new ApolloClient({ 114 | link: ApolloLink.from([ 115 | //if errors are returned from client, log to the console and 116 | onError(({ graphQLErrors, networkError }) => { 117 | if (graphQLErrors) 118 | graphQLErrors.forEach(({ message, locations, path }) => { 119 | console.log( 120 | `[GraphQL error]: Message: ${message}, Location: ${JSON.stringify(locations)}, Path: ${path}`, 121 | ); 122 | //save graphQLErrors to response to be rendered in catch of addQuery() 123 | r = JSON.stringify(graphQLErrors); 124 | } 125 | ); 126 | if (networkError) console.log(`[Network error]: ${networkError}`); 127 | }), 128 | new HttpLink({ 129 | uri: apolloServerURI, 130 | credentials: 'same-origin' 131 | }) 132 | ]), 133 | cache: new InMemoryCache() 134 | }); 135 | 136 | // Adds the query to state 137 | function addQuery() { 138 | //"q" represents the user defined query 139 | let q = document.getElementById("query").value; 140 | 141 | //packages and sends query to send to the graphQL enpoint through the client established above 142 | client.query({ 143 | query: gql`${q}` 144 | }).then(result => { 145 | console.log('apollo server result: ', result); 146 | //saves stringified result to state in the "queries" array 147 | r = JSON.stringify({data: JSON.stringify(result.data)}) ; 148 | //writes response from GraphQL to "Response" text box in DOM 149 | document.getElementById("response").value = r; 150 | //updating state 151 | dispatch({ type: UPDATE_QUERIES, payload: [[q], [r]] }); 152 | }).catch(error => { 153 | //if no graphQLError was returned from apolloClient, assign error to response 154 | if(r.length == 0){ 155 | r = 'Unknown error occured'; 156 | } 157 | document.getElementById("response").value = r; 158 | dispatch({ type: UPDATE_QUERIES, payload: [[q], [r]] }); 159 | }) 160 | } 161 | 162 | // toggles the instructions pop up 163 | function showInstructions() { 164 | // console.log("You clicked the instructions icon/button") 165 | dispatch({ type: SET_POP_UP, payload: 'instructions'}); 166 | } 167 | 168 | // updates the endpoint url in state when submit button is clicked 169 | function updateURL() { 170 | let url = document.getElementById('url').value; 171 | if (url.match(/http:\/\/.+/) || url.match(/https:\/\/.+/)) { 172 | dispatch({ type: ADD_APOLLO_SERVER_URI, payload: url }); 173 | console.log('test updateURL', apolloServerURI); 174 | document.querySelector('#endpointStatus').innerHTML = `Current endpoint: ${document.getElementById('url').value}`; 175 | document.querySelector('#endpointStatus').classList.remove('invisible'); 176 | document.getElementById('url').value = ''; 177 | } else { 178 | document.querySelector('#endpointStatus').innerHTML = "Endpoint must include http:// or https://"; 179 | document.querySelector('#endpointStatus').classList.remove('invisible'); 180 | } 181 | } 182 | 183 | useEffect(() => { 184 | let display = ''; 185 | for(let i = 0; i < queries[0].length; i++) { 186 | display += `

${queries[0][i]}

`; 187 | } 188 | document.getElementById("queriesDisplay").innerHTML = display; 189 | }); 190 | 191 | 192 | /*-------------------- Functional Component Rendering --------------------*/ 193 | //FOR FUTURE IMPLEMENTATION: Check the user input and maybe ping the endpoint to check that it is live. 194 | return( 195 |
196 | 197 | 198 |
199 |
200 | 203 |
204 |
205 | 206 |

Current endpoint:

207 |
208 |
209 |
210 | 211 | Insert Your Query Here 212 | 213 | 214 | View Your Query Response Below 215 | 216 | 217 | 218 | Test Queries to Export 219 |
220 | 224 |
225 |
226 |
227 | ); 228 | } 229 | export default TestsView; -------------------------------------------------------------------------------- /src/components/view/visualizeView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from 'react'; 2 | import { Store } from '../../state/store'; 3 | import * as d3 from 'd3'; 4 | import styled from 'styled-components'; 5 | import VisualizerSideBar from '../sideBar/visualizerSidebar'; 6 | 7 | /*-------------------- Styled Components --------------------*/ 8 | 9 | // styles the right side-bar that appears after clicking the 'Visualize' button 10 | const Container = styled.div` 11 | display: grid; 12 | grid-template-columns: repeat(5, 1fr); 13 | grid-template-areas: 14 | "viz viz viz viz bar"; 15 | height: calc(100vh - 64px); 16 | background-color: '#EEEFF0'; 17 | font-family: "Roboto", sans-serif; 18 | `; 19 | 20 | // styles the graph area that appears after clicking the 'Visualize' button 21 | const Viz = styled.div` 22 | grid-area: viz; 23 | background-color: #EEEFF0; 24 | font-family: "Roboto", sans-serif; 25 | `; 26 | 27 | 28 | /*-------------------- Functional Component --------------------*/ 29 | 30 | function VisualizeView() { 31 | 32 | /* 33 | -> connects the application to the context (utilized by Hooks in React) and facilitates the ability to 34 | update the context of the application 35 | -> the context is initialized by useContext() and specified by Store which is found 36 | in /components/state/store.jsx 37 | */ 38 | const { state: { visualizeJSON } } = useContext(Store); 39 | 40 | const treeData = visualizeJSON; 41 | 42 | function createViz() { 43 | 44 | function responsivefy(svg) { 45 | // use D3 to create the graph that appears after clicking the 'Visualize' button 46 | const container = d3.select(svg.node().parentNode), 47 | width = parseInt(svg.style("width")), 48 | height = parseInt(svg.style("height")); 49 | svg.attr("viewBox", "0 0 " + width + " " + height) 50 | .attr("perserveAspectRatio", "xMidYMid") 51 | .call(resize); 52 | d3.select(window).on("resize." + container.attr("id"), resize); 53 | 54 | function resize() { 55 | const targetWidth = parseInt(container.style("width")); 56 | svg.attr("width", targetWidth); 57 | svg.attr("height", "calc(100vh - 70px)"); 58 | } 59 | } 60 | 61 | const fillColor = "#F2EEF6"; 62 | 63 | const margin = { top: 20, right: 120, bottom: 20, left: 80 }, 64 | width = 700 - margin.left - margin.right, 65 | height = 600 - margin.top - margin.bottom; 66 | 67 | const svg = d3.select("#vizView").append("svg") 68 | .attr("width", width + margin.right + margin.left) 69 | .attr("height", height + margin.top + margin.bottom) 70 | .call(responsivefy) 71 | .append("g") 72 | .attr("transform", "translate(" + margin.left + "," + margin.top + ")") 73 | 74 | let i = 0; 75 | const duration = 750; 76 | let root; 77 | 78 | const treemap = d3.tree().size([height, width]); 79 | 80 | root = d3.hierarchy(treeData, function (d) { return d.children; }); 81 | root.x0 = height / 2; 82 | root.y0 = 0; 83 | 84 | 85 | function update(source) { 86 | 87 | const treeData = treemap(root); 88 | const nodes = treeData.descendants(); 89 | const links = treeData.descendants().slice(1); 90 | 91 | nodes.forEach(function (d) { d.y = d.depth * 180 }); 92 | 93 | const node = svg.selectAll('g.node') 94 | .data(nodes, function (d) { return d.id || (d.id = ++i); }); 95 | 96 | const nodeEnter = node.enter().append('g') 97 | .attr('class', 'node') 98 | .attr("transform", function (d) { 99 | return "translate(" + source.y0 + "," + source.x0 + ")"; 100 | }) 101 | .on('click', click); 102 | 103 | nodeEnter.append('ellipse') 104 | .attr('class', 'ellipse') 105 | .style("fill", function (d) { 106 | return d._children ? fillColor : "#fff"; 107 | }) 108 | 109 | nodeEnter.append('text') 110 | .attr("dy", ".35em") 111 | .attr("text-anchor", 'middle') 112 | .text(function (d) { 113 | if (d.depth % 2 === 0) return d.data.name; 114 | return `${d.data.name}: ${d.data.type}` 115 | }) 116 | .attr('font-size', '0.7em') 117 | .attr('cursor', 'pointer'); 118 | 119 | const nodeUpdate = nodeEnter.merge(node); 120 | 121 | nodeUpdate.transition() 122 | .duration(duration) 123 | .attr("transform", function (d) { 124 | return "translate(" + d.y + "," + d.x + ")"; 125 | }); 126 | 127 | nodeUpdate.select('ellipse.node') 128 | .style("fill", function (d) { 129 | return d._children ? fillColor : "#fff"; 130 | }) 131 | .attr('cursor', 'pointer'); 132 | 133 | const nodeExit = node.exit().transition() 134 | .duration(duration) 135 | .attr("transform", function (d) { 136 | return "translate(" + source.y + "," + source.x + ")"; 137 | }) 138 | .remove(); 139 | 140 | nodeExit.select('ellipse') 141 | .attr('rx', 1e-6) 142 | .attr('ry', 1e-6); 143 | 144 | nodeExit.select('text') 145 | .style('fill-opacity', 1e-6); 146 | 147 | const link = svg.selectAll('path.link') 148 | .data(links, function (d) { return d.id; }); 149 | 150 | const linkEnter = link.enter().insert('path', "g") 151 | .attr("class", "link") 152 | .attr('d', function (d) { 153 | const o = { x: source.x0, y: source.y0 } 154 | return diagonal(o, o) 155 | }); 156 | 157 | const linkUpdate = linkEnter.merge(link); 158 | 159 | linkUpdate.transition() 160 | .duration(duration) 161 | .attr('d', function (d) { return diagonal(d, d.parent) }); 162 | 163 | const linkExit = link.exit().transition() 164 | .duration(duration) 165 | .attr('d', function (d) { 166 | const o = { x: source.x, y: source.y } 167 | return diagonal(o, o) 168 | }) 169 | .remove(); 170 | 171 | nodes.forEach(function (d) { 172 | d.x0 = d.x; 173 | d.y0 = d.y; 174 | }); 175 | 176 | function diagonal(s, d) { 177 | 178 | const path = `M ${s.y} ${s.x} 179 | C ${(s.y + d.y) / 2} ${s.x}, 180 | ${(s.y + d.y) / 2} ${d.x}, 181 | ${d.y} ${d.x}` 182 | 183 | return path; 184 | } 185 | 186 | function click(d) { 187 | if (d.children) { 188 | d._children = d.children; 189 | d.children = null; 190 | } else { 191 | d.children = d._children; 192 | d._children = null; 193 | } 194 | update(d); 195 | } 196 | 197 | d3.selectAll('path') 198 | .attr('fill', 'none') 199 | .attr('stroke', '#DD399C') 200 | .attr('stroke-width', 2) 201 | .attr('stroke-opacity', '1') 202 | 203 | d3.selectAll('ellipse') 204 | .attr('rx', 86) 205 | .attr('ry', 21) 206 | .attr('stroke', 'rgba(0, 0, 0, 0.18') 207 | .attr('stroke-width', 0.5) 208 | .attr('fill', 'white') 209 | 210 | // GLOW effect 211 | const defs = svg.append("defs"); 212 | const colors = ["#A852E5", "#14BDEB", "#0D18E8"]; 213 | 214 | //set color (white glow) for parent node 215 | const filter0 = defs.append("filter") 216 | .attr("id", "glow0"); 217 | filter0.append("feGaussianBlur") 218 | .attr("stdDeviation", "5") 219 | .attr("result", "coloredBlur"); 220 | const feMerge0 = filter0.append("feMerge"); 221 | feMerge0.append("feMergeNode") 222 | .attr("in", "coloredBlur"); 223 | feMerge0.append("feMergeNode") 224 | .attr("in", "SourceGraphic"); 225 | 226 | // set color for queries 227 | const filter1 = defs.append("filter") 228 | .attr("id", "glow1"); 229 | 230 | filter1.append("feMorphology") 231 | .attr("operator", "dilate") 232 | .attr("radius", 1.5) 233 | .attr("in", "SourceAlpha") 234 | .attr("result", "thicken"); 235 | 236 | filter1.append("feGaussianBlur") 237 | .attr("in", "thicken") 238 | .attr("stdDeviation", "2") 239 | .attr("result", "blurred"); 240 | 241 | filter1.append("feFlood") 242 | .attr("flood-color", colors[0]) 243 | .attr("result", "glowColor") 244 | 245 | filter1.append("feComposite") 246 | .attr("in", "glowColor") 247 | .attr("in2", "blurred") 248 | .attr("operator", "in") 249 | .attr("result", "softGlow_colored") 250 | 251 | const feMerge1 = filter1.append("feMerge"); 252 | feMerge1.append("feMergeNode") 253 | .attr("in", "softGlow_colored"); 254 | feMerge1.append("feMergeNode") 255 | .attr("in", "SourceGraphic"); 256 | 257 | // set color for types 258 | const filter2 = defs.append("filter") 259 | .attr("id", "glow2"); 260 | 261 | filter2.append("feMorphology") 262 | .attr("operator", "dilate") 263 | .attr("radius", 1.5) 264 | .attr("in", "SourceAlpha") 265 | .attr("result", "thicken"); 266 | 267 | filter2.append("feGaussianBlur") 268 | .attr("in", "thicken") 269 | .attr("stdDeviation", "2") 270 | .attr("result", "blurred"); 271 | 272 | filter2.append("feFlood") 273 | .attr("flood-color", colors[1]) 274 | .attr("result", "glowColor") 275 | 276 | filter2.append("feComposite") 277 | .attr("in", "glowColor") 278 | .attr("in2", "blurred") 279 | .attr("operator", "in") 280 | .attr("result", "softGlow_colored") 281 | 282 | const feMerge2 = filter2.append("feMerge"); 283 | feMerge2.append("feMergeNode") 284 | .attr("in", "softGlow_colored"); 285 | feMerge2.append("feMergeNode") 286 | .attr("in", "SourceGraphic"); 287 | 288 | // set color for queryable 289 | const filter3 = defs.append("filter") 290 | .attr("id", "glow3"); 291 | 292 | filter3.append("feMorphology") 293 | .attr("operator", "dilate") 294 | .attr("radius", 1.5) 295 | .attr("in", "SourceAlpha") 296 | .attr("result", "thicken"); 297 | 298 | filter3.append("feGaussianBlur") 299 | .attr("in", "thicken") 300 | .attr("stdDeviation", "2") 301 | .attr("result", "blurred"); 302 | 303 | filter3.append("feFlood") 304 | .attr("flood-color", colors[2]) 305 | .attr("result", "glowColor") 306 | 307 | filter3.append("feComposite") 308 | .attr("in", "glowColor") 309 | .attr("in2", "blurred") 310 | .attr("operator", "in") 311 | .attr("result", "softGlow_colored") 312 | 313 | const feMerge3 = filter3.append("feMerge"); 314 | feMerge3.append("feMergeNode") 315 | .attr("in", "softGlow_colored"); 316 | feMerge3.append("feMergeNode") 317 | .attr("in", "SourceGraphic"); 318 | 319 | 320 | d3.selectAll(".ellipse") 321 | .style("filter", (d) => { 322 | if (d.depth === 0) return "url(#glow0)"; 323 | if (d.depth === 1) return "url(#glow1)"; 324 | if (d.depth === 2) return "url(#glow2)"; 325 | if (d.depth === 3) return "url(#glow3)"; 326 | }) 327 | .style("fill", function (d) { 328 | return d._children ? fillColor : "#fff"; 329 | }); 330 | 331 | } 332 | update(root); 333 | } 334 | 335 | useEffect(createViz, []) 336 | 337 | return ( 338 | 339 | 340 | 341 | 342 | ); 343 | } 344 | 345 | 346 | 347 | export default VisualizeView; -------------------------------------------------------------------------------- /src/container/mainContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from '../components/header/header'; 3 | import NavSideBar from '../components/sideBar/navSidebar'; 4 | import Welcome from '../components/popup/welcome'; 5 | import Instructions from '../components/popup/instructions'; 6 | import MainView from '../components/view/mainView'; 7 | import ExportPopUp from '../components/popup/exportPopUp'; 8 | import styled from 'styled-components'; 9 | 10 | /*-------------------- Styled Component --------------------*/ 11 | 12 | /* 13 | styles the display area that appears after you exit the ReadME dialog-box that appears when you load the 14 | application 15 | */ 16 | const Container = styled.div` 17 | display: grid; 18 | grid-template-columns: repeat(7, 1fr); 19 | grid-template-rows: 65px auto; 20 | grid-template-areas: 21 | "header header header header header header header" 22 | "navSideBar main main main main main main"; 23 | height: 100vh; 24 | background-color: #EEEFF0; 25 | font-family: "Roboto", sans-serif; 26 | `; 27 | 28 | /*-------------------- Functional Component --------------------*/ 29 | 30 | const Main = () => { 31 | 32 | return ( 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | export default Main; 45 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const ReactDOM = require("react-dom"); 3 | const { App } = require("./App"); 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById("root") 8 | ); 9 | 10 | -------------------------------------------------------------------------------- /src/pg-import/pgQuery.js: -------------------------------------------------------------------------------- 1 | // const pool = require('./sqlPool'); 2 | const { Pool } = require('pg'); 3 | const dotenv = require('dotenv'); 4 | 5 | 6 | const pgQuery = async function(fetchedTables) { 7 | //reads the .env config after latest update, allowing us to use new tables if a previous import has run 8 | dotenv.config(); 9 | const URI = process.env.DB_URI 10 | 11 | const tableQuery = ` 12 | SELECT table_name, 13 | ordinal_position as position, 14 | column_name, 15 | data_type, 16 | is_nullable, 17 | column_default as default_value 18 | FROM information_schema.columns 19 | WHERE table_schema not in ('information_schema', 'pg_catalog') AND 20 | table_name not in ('pg_stat_statements') 21 | ORDER BY table_schema, 22 | table_name, 23 | ordinal_position;`; 24 | 25 | const constraintQuery = ` 26 | SELECT table_name, 27 | column_name, 28 | constraint_name 29 | FROM information_schema.constraint_column_usage 30 | WHERE table_schema not in ('information_schema', 'pg_catalog') AND 31 | table_name not in ('pg_stat_statements') 32 | ORDER BY table_name;` 33 | 34 | const pool = new Pool({ 35 | connectionString: URI, 36 | ssl: true, 37 | }) 38 | // console.log('db uri pre pool.connect:', URI) 39 | 40 | pool.connect((err, client, done) => { 41 | if (err) return console.log(`Error connecting to db, ${err}`); 42 | console.log('Connected to db 😄') 43 | done(); 44 | }) 45 | 46 | let queryResults = await pool.query(tableQuery) 47 | .then(res => { 48 | // console.log("pool: ",pool) 49 | // console.log('db URI: ', URI) 50 | // console.log('tableQuery: ',res.rows) 51 | const tableColumns = res.rows; 52 | return tableColumns; 53 | }) 54 | .then(tableColumns => { 55 | return pool.query(constraintQuery) 56 | .then(res => { 57 | // console.log('constraintQuery: ',res.rows) 58 | const result = res.rows; 59 | for(let item of result){ 60 | // console.log("tablecolumns: ", tableColumns) 61 | let table = item.table_name; 62 | let col = item.column_name; 63 | for(let column of tableColumns){ 64 | // console.log('column: ', column, "col: ", col) 65 | if(column.table_name == table && column.column_name == col){ 66 | column.constraint = item.constraint_name; 67 | } 68 | } 69 | } 70 | const tables = {}; 71 | let index = -1; 72 | tableColumns.forEach((item) => { 73 | //if table type matches index, do not create new table index 74 | if(!tables[index] || tables[index].type !== item.table_name){ 75 | //increment index to move on to new table 76 | index++ 77 | tables[index] = {}; 78 | tables[index].type = item.table_name; 79 | tables[index].fields = {}; 80 | tables[index].fieldIndex = 0; 81 | tables[index].tableID = index.toString(); 82 | tables[index].fields = []; 83 | } 84 | tables[index].fieldIndex ++; 85 | 86 | // tables[index].fields[position] = {}; 87 | const column = {}; 88 | column.name = item.column_name; 89 | 90 | //assigns column data types from Postgres to match our app's data types 91 | switch(item.data_type){ 92 | case "character varying": 93 | column.type = "String"; 94 | break; 95 | case "integer": 96 | column.type = "Int"; 97 | break; 98 | case "serial": 99 | column.type = "ID"; 100 | break; 101 | case "boolean": 102 | column.type = "Boolean" 103 | } 104 | column.required = item.is_nullable == "YES" ? true : false; 105 | column.tableNum = index.toString(); 106 | column.fieldNum = item.position; 107 | column.defaultValue = item.default_value ? item.default_value : ""; 108 | //values that need to be fetched from db 109 | column.primaryKey = false; 110 | column.unique = false; 111 | column.relationSelected = false; 112 | column.relation = { 113 | tableIndex: -1, 114 | fieldIndex: -1, 115 | refType: '' 116 | }, 117 | column.queryable = true; 118 | 119 | tables[index].fields.push(column) 120 | }) 121 | // console.log("tables (pgquery): ", tables) 122 | fetchedTables = tables; 123 | return fetchedTables; 124 | // console.log('final table column obj: ', tableColumns) 125 | }) 126 | 127 | }) 128 | .catch(err => console.error('Error is: ', err)) 129 | return queryResults; 130 | } 131 | 132 | module.exports = pgQuery; 133 | 134 | -------------------------------------------------------------------------------- /src/pg-import/sqlPool.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | require("dotenv").config(); 3 | 4 | const URI = process.env.DB_URI 5 | 6 | const pool = new Pool({ 7 | connectionString: URI, 8 | ssl: true, 9 | }) 10 | 11 | pool.connect((err, client, done) => { 12 | if (err) return console.log(`Error connecting to db, ${err}`); 13 | console.log('Connected to db 😄') 14 | done(); 15 | }) 16 | 17 | module.exports = pool; -------------------------------------------------------------------------------- /src/state/initialState.jsx: -------------------------------------------------------------------------------- 1 | export const selectedTable = { 2 | type: '', 3 | fields: {}, 4 | fieldIndex: 1, 5 | tableID: 0 6 | }; 7 | 8 | // specifies the initial values of the input table that appears when a user clicks 'Add Table' 9 | export const initialTable = { 10 | type: '', 11 | fields: { 12 | 1: { 13 | name: '', 14 | type: 'ID', 15 | primaryKey: false, 16 | unique: false, 17 | defaultValue: '', 18 | required: false, 19 | relationSelected: false, 20 | relation: { 21 | tableIndex: -1, 22 | fieldIndex: -1, 23 | refType: '' 24 | }, 25 | queryable: true, 26 | tableNum: 0, 27 | fieldNum: 1, 28 | } 29 | }, 30 | fieldIndex: 2, 31 | tableID: 0 32 | }; 33 | 34 | /* 35 | specifies the initial values of the input table that appears when a user clicks 'Add Field' within 36 | the input table that appears after a user clicks 'Add Table' 37 | */ 38 | export const initialField = { 39 | name: '', 40 | type: 'ID', 41 | primaryKey: false, 42 | unique: false, 43 | defaultValue: '', 44 | required: false, 45 | relationSelected: false, 46 | relation: { 47 | tableIndex: -1, 48 | fieldIndex: -1, 49 | refType: '' 50 | }, 51 | queryable: true, 52 | tableNum: -1, 53 | fieldNum: -1, 54 | }; 55 | 56 | export const tableIndex = 0; 57 | 58 | export const tables = {}; 59 | 60 | //this will toggle the tab shown in the sandbox area 61 | //potential tabs for MVP are code & schema, stretch would include GQL setup area 62 | export const view = 'schema'; 63 | 64 | //this will toggle popups 65 | //potential popups are welcome and export (select folder to save & success) 66 | export const popUp = 'welcome'; 67 | 68 | export const visualizeJSON = { "name": "Queries" }; 69 | 70 | export const gqlSchema = `const { gql } = require('apollo-server-express');\n\nmodule.exports = typeDefs;\n`; 71 | 72 | export const gqlResolvers = `const pool = require('../db/sqlPool');\n\nmodule.exports = resolvers;\n`; 73 | 74 | export const sqlScripts = ``; 75 | 76 | //An array of arrays index 0 is the queries. index 1 is the responses to the queries 77 | export const queries = [[], []]; 78 | 79 | // stores the Apollo server URI input by the user in tests view 80 | export const apolloServerURI = 'http://localhost:3000/GraphQL'; 81 | 82 | export const displayError = { displayStatus: false, throttleStatus: true, relatedTable: -1, relatedField: -1 }; 83 | -------------------------------------------------------------------------------- /src/state/mockState.jsx: -------------------------------------------------------------------------------- 1 | // This file contains mock state data to be used for development purposes 2 | 3 | export const selectedTable = { 4 | type: '', 5 | fields: {}, 6 | fieldIndex: 1, 7 | tableID: -1 8 | }; 9 | 10 | export const initialTable = { 11 | type: '', 12 | fields: { 13 | 1: { 14 | name: '', 15 | type: 'ID', 16 | primaryKey: false, 17 | unique: false, 18 | defaultValue: '', 19 | required: false, 20 | relationSelected: false, 21 | relation: { 22 | tableIndex: -1, 23 | fieldIndex: -1, 24 | refType: '' 25 | }, 26 | queryable: true, 27 | tableNum: tableIndex, 28 | fieldNum: 1, 29 | } 30 | }, 31 | fieldIndex: 2, 32 | tableID: -1 33 | }; 34 | 35 | export const initialField = { 36 | name: '', 37 | type: 'ID', 38 | primaryKey: false, 39 | unique: false, 40 | required: false, 41 | defaultValue: '', 42 | relationSelected: false, 43 | relation: { 44 | tableIndex: -1, 45 | fieldIndex: -1, 46 | refType: '' 47 | }, 48 | tableNum: -1, 49 | fieldNum: -1, 50 | queryable: true 51 | }; 52 | 53 | export const tableIndex = 2; 54 | 55 | export const tables = { 56 | 0: { 57 | type: 'Author', 58 | fields: { 59 | 0: { 60 | name: 'id', 61 | type: 'ID', 62 | primaryKey: true, 63 | unique: true, 64 | required: false, 65 | defaultValue: '', 66 | relationSelected: false, 67 | relation: { 68 | tableIndex: -1, 69 | fieldIndex: -1, 70 | refType: '' 71 | }, 72 | tableNum: 0, 73 | fieldNum: 0, 74 | queryable: false 75 | }, 76 | 1: { 77 | name: 'first_name', 78 | type: 'String', 79 | primaryKey: false, 80 | unique: false, 81 | required: true, 82 | defaultValue: '', 83 | relationSelected: false, 84 | relation: { 85 | tableIndex: -1, 86 | fieldIndex: -1, 87 | refType: '' 88 | }, 89 | tableNum: 0, 90 | fieldNum: 1, 91 | queryable: false 92 | }, 93 | 2: { 94 | name: 'last_name', 95 | type: 'String', 96 | primaryKey: false, 97 | unique: false, 98 | required: true, 99 | defaultValue: '', 100 | relationSelected: false, 101 | relation: { 102 | tableIndex: -1, 103 | fieldIndex: -1, 104 | refType: '' 105 | }, 106 | tableNum: 0, 107 | fieldNum: 2, 108 | queryable: false 109 | } 110 | }, 111 | fieldIndex: 3, 112 | tableID: 0 113 | }, 114 | 1: { 115 | type: 'Books', 116 | fields: { 117 | 0: { 118 | name: 'id', 119 | type: 'ID', 120 | primaryKey: true, 121 | unique: true, 122 | required: false, 123 | defaultValue: '', 124 | relationSelected: false, 125 | relation: { 126 | tableIndex: -1, 127 | fieldIndex: -1, 128 | refType: '' 129 | }, 130 | tableNum: 1, 131 | fieldNum: 0, 132 | queryable: true 133 | }, 134 | 1: { 135 | name: 'name', 136 | type: 'String', 137 | primaryKey: false, 138 | unique: false, 139 | required: true, 140 | defaultValue: '', 141 | relationSelected: false, 142 | relation: { 143 | tableIndex: -1, 144 | fieldIndex: -1, 145 | refType: '' 146 | }, 147 | tableNum: 1, 148 | fieldNum: 1, 149 | queryable: true 150 | }, 151 | 2: { 152 | name: 'author_id', 153 | type: 'ID', 154 | primaryKey: false, 155 | unique: false, 156 | required: true, 157 | defaultValue: '', 158 | relationSelected: true, 159 | relation: { 160 | tableIndex: '0', 161 | fieldIndex: '0', 162 | refType: 'many to one' 163 | }, 164 | tableNum: 1, 165 | fieldNum: 2, 166 | queryable: true 167 | } 168 | }, 169 | fieldIndex: 3, 170 | tableID: 1 171 | }, 172 | }; 173 | 174 | export const view = 'table'; 175 | 176 | export const popUp = 'welcome'; 177 | 178 | export const visualizeJSON = { "name": "Queries", "children": [{ "name": "getAllAuthor", "type": "[Author]", "children": [{ "name": "Author", "type": "[Author]" }] }, { "name": "getAuthor", "type": "[Author]", "children": [{ "name": "Author", "type": "[Author]", "children": [{ "name": "id", "type": "ID" }, { "name": "first_name", "type": "String" }, { "name": "last_name", "type": "String" }] }] }, { "name": "getAllBooks", "type": "[Books]", "children": [{ "name": "Books", "type": "[Books]" }] }, { "name": "getBooks", "type": "[Books]", "children": [{ "name": "Books", "type": "[Books]", "children": [{ "name": "id", "type": "ID" }, { "name": "name", "type": "String" }, { "name": "author_id", "type": "ID" }] }] }] } 179 | 180 | export const gqlSchema = `const { gql } = require('apollo-server-express'); 181 | 182 | const typeDefs = gql\` 183 | 184 | type Author { 185 | id: ID 186 | first_name: String! 187 | last_name: String! 188 | } 189 | 190 | type Books { 191 | id: ID 192 | name: String! 193 | author: Author 194 | } 195 | 196 | type Query { 197 | getAllAuthor: [Author] 198 | getAllBooks: [Books] 199 | getBooks( 200 | id: ID, 201 | name: String, 202 | author_id: ID 203 | ): [Books] 204 | } 205 | 206 | \`; 207 | 208 | module.exports = typeDefs;`; 209 | 210 | export const gqlResolvers = `const pool = require('../db/sqlPool'); 211 | 212 | const resolvers = { 213 | 214 | Query: { 215 | getAllAuthor() { 216 | const sql = \`SELECT * FROM "Author";\`; 217 | return pool.query(sql) 218 | .then(res => res.rows) 219 | .catch(err => console.error('Error is: ', err)); 220 | }, 221 | getAllBooks() { 222 | const sql = \`SELECT * FROM "Books";\`; 223 | return pool.query(sql) 224 | .then(res => res.rows) 225 | .catch(err => console.error('Error is: ', err)); 226 | }, 227 | getBooks(parent, args, context, info) { 228 | let sql = \`SELECT * FROM "Books"\`; 229 | let whereClause = \` WHERE \`; 230 | Object.keys(args).forEach((fieldName, i , arr) => { 231 | whereClause += \`"\${fieldName}" = '\${args[fieldName]}'\`; 232 | if (i !== arr.length - 1) whereClause += \` AND \`; 233 | else whereClause += \`;\`; 234 | }); 235 | sql += whereClause; 236 | return pool.query(sql) 237 | .then(res => res.rows) 238 | .catch(err => console.error('Error is: ', err)); 239 | }, 240 | }, 241 | 242 | Author: { 243 | id: (parent, args, context, info) => { 244 | return parent.id; 245 | }, 246 | first_name: (parent, args, context, info) => { 247 | return parent.first_name; 248 | }, 249 | last_name: (parent, args, context, info) => { 250 | return parent.last_name; 251 | }, 252 | }, 253 | 254 | Books: { 255 | id: (parent, args, context, info) => { 256 | return parent.id; 257 | }, 258 | name: (parent, args, context, info) => { 259 | return parent.name; 260 | }, 261 | author: (parent, args, context, info) => { 262 | const sql = \`SELECT * FROM "Author" WHERE "id" = '\${parent.author_id}';\`; 263 | return pool.query(sql) 264 | .then(res => res.rows[0]) 265 | .catch(err => console.error('Error is: ', err)) 266 | }, 267 | }, 268 | 269 | } 270 | 271 | module.exports = resolvers;`; 272 | 273 | export const sqlScripts = `CREATE TABLE "Author"( 274 | "id" SERIAL PRIMARY KEY UNIQUE, 275 | "first_name" VARCHAR(256) NOT NULL, 276 | "last_name" VARCHAR(256) NOT NULL 277 | ); 278 | 279 | CREATE TABLE "Books"( 280 | "id" SERIAL PRIMARY KEY UNIQUE, 281 | "name" VARCHAR(256) NOT NULL, 282 | "author_id" SERIAL NOT NULL 283 | );`; 284 | 285 | export const displayError = { displayStatus: false, throttleStatus: true, relatedTable: -1, relatedField: -1 }; 286 | -------------------------------------------------------------------------------- /src/state/store.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-case-declarations */ 2 | import React from 'react'; 3 | import * as state from '../state/initialState'; 4 | import deepClone from '../utils/deepClone'; 5 | import buildGQLSchema from '../utils/buildGQLSchema'; 6 | import buildGQLResolvers from '../utils/buildGQLResolvers'; 7 | import buildSQLScripts from '../utils/buildSQLScripts'; 8 | import { buildVisualizerJson } from '../utils/buildVisualizerJson'; 9 | 10 | // sets the intial state of the application as found in '../state/initialState' 11 | const initialState = state; 12 | 13 | function reducer(state, action) { 14 | 15 | // makes a deep clone of 'state' to enable updates of 'state' 16 | const newState = deepClone(state); 17 | let selectedTable; 18 | let tables; 19 | 20 | switch (action.type) { 21 | case "SET_POP_UP": 22 | return { ...state, popUp: action.payload }; 23 | 24 | case "ADD_TABLE": 25 | selectedTable = newState.initialTable; 26 | selectedTable.tableID = newState.tableIndex; 27 | selectedTable.fields[1].tableNum = newState.tableIndex; 28 | return { ...state, selectedTable, tableIndex: newState.tableIndex + 1 }; 29 | 30 | case "ADD_FIELD": 31 | // Assign which table and field this newly added field belongs to 32 | newState.initialField.tableNum = newState.selectedTable.tableID; 33 | newState.initialField.fieldNum = newState.selectedTable.fieldIndex; 34 | 35 | // Add the new field to our selectedTable and increment the field index on our selectedTable 36 | newState.selectedTable.fields[newState.selectedTable.fieldIndex++] = newState.initialField; 37 | return { ...state, selectedTable: newState.selectedTable }; 38 | 39 | case "EDIT_TABLE": 40 | newState.selectedTable = newState.tables[action.payload]; 41 | return { ...state, selectedTable: newState.selectedTable }; 42 | 43 | case "EDIT_FIELD": 44 | const { fieldKey, fieldProperty, value } = action.payload; 45 | newState.selectedTable.fields[fieldKey][fieldProperty] = value; 46 | return { ...state, selectedTable: newState.selectedTable }; 47 | 48 | case "EDIT_RELATIONS": 49 | const { relationFieldKey, relationFieldProperty, relationValue } = action.payload; 50 | const currentRelation = newState.selectedTable.fields[relationFieldKey].relation; 51 | currentRelation[relationFieldProperty] = relationValue; 52 | if (currentRelation.tableIndex !== -1) newState.selectedTable.fields[relationFieldKey].relationSelected = true; 53 | else newState.selectedTable.fields[relationFieldKey].relationSelected = false; 54 | return { ...state, selectedTable: newState.selectedTable }; 55 | 56 | case "EDIT_TABLE_NAME": 57 | newState.selectedTable.type = action.payload; 58 | return { ...state, selectedTable: newState.selectedTable }; 59 | 60 | // This case will increment tableIndex regardless whether we're adding a new table or editing an existing one 61 | case "SAVE_TABLE": 62 | newState.tables[newState.selectedTable.tableID] = newState.selectedTable; 63 | 64 | return { 65 | ...state, 66 | tables: newState.tables, 67 | tableIndex: newState.tableIndex + 1, 68 | visualizeJSON: buildVisualizerJson(deepClone(newState.tables)), 69 | gqlSchema: buildGQLSchema(newState.tables), 70 | gqlResolvers: buildGQLResolvers(newState.tables), 71 | sqlScripts: buildSQLScripts(newState.tables) 72 | }; 73 | 74 | //Import tables from existing postgres database 75 | case "IMPORT_TABLES": 76 | newState.tables = action.payload; 77 | const indeces = Object.keys(newState.tables); 78 | console.log("indeces",indeces) 79 | const newTableIndex = indeces[indeces.length - 1] 80 | 81 | return { 82 | ...state, 83 | tables: newState.tables, 84 | tableIndex: newTableIndex + 1, 85 | visualizeJSON: buildVisualizerJson(deepClone(newState.tables)), 86 | gqlSchema: buildGQLSchema(newState.tables), 87 | gqlResolvers: buildGQLResolvers(newState.tables), 88 | sqlScripts: buildSQLScripts(newState.tables) 89 | }; 90 | 91 | case "DELETE_TABLE": 92 | tables = Object.values(newState.tables); 93 | for (let table of tables) { 94 | const fields = Object.values(table.fields); 95 | for (let field of fields) { 96 | if (Number(field.relation.tableIndex) === Number(action.payload)) { 97 | newState.displayError.displayStatus = true; 98 | newState.displayError.relatedTable = table.type; 99 | newState.displayError.relatedField = field.name; 100 | return { ...state, displayError: newState.displayError }; 101 | } 102 | } 103 | } 104 | delete newState.tables[action.payload]; 105 | return { 106 | ...state, 107 | tables: newState.tables, 108 | visualizeJSON: buildVisualizerJson(deepClone(newState.tables)), 109 | gqlSchema: buildGQLSchema(newState.tables), 110 | gqlResolvers: buildGQLResolvers(newState.tables), 111 | sqlScripts: buildSQLScripts(newState.tables) 112 | }; 113 | 114 | case "DELETE_FIELD": 115 | tables = Object.values(newState.tables); 116 | for (let table of tables) { 117 | const fields = Object.values(table.fields); 118 | for (let field of fields) { 119 | if (Number(field.relation.tableIndex) === Number(newState.selectedTable.tableID) && field.relation.fieldIndex === action.payload) { 120 | newState.displayError.displayStatus = true; 121 | newState.displayError.relatedTable = table.type; 122 | newState.displayError.relatedField = field.name; 123 | return { ...state, displayError: newState.displayError }; 124 | } 125 | } 126 | } 127 | 128 | delete newState.selectedTable.fields[action.payload]; 129 | return { ...state, selectedTable: newState.selectedTable }; 130 | 131 | case "SET_VIEW": 132 | return { ...state, view: action.payload }; 133 | 134 | case "HIDE_DISPLAY_ERROR": 135 | newState.displayError.displayStatus = false; 136 | return { ...state, displayError: newState.displayError }; 137 | 138 | case "THROTTLE_DISPLAY_ERROR": 139 | newState.displayError.throttleStatus = !newState.displayError.throttleStatus; 140 | return { ...state, displayError: newState.displayError }; 141 | 142 | //Updates the queries that are input by the user and the subsequent responses. 143 | case "UPDATE_QUERIES": 144 | console.log(action.payload[0][0]); 145 | return { 146 | ...state, 147 | queries: [state.queries[0].concat(action.payload[0][0]), state.queries[1].concat(action.payload[1][0])] 148 | } 149 | //Updates the endpoint as input by the user. 150 | case "ADD_APOLLO_SERVER_URI": 151 | return { 152 | ...state, 153 | apolloServerURI: action.payload, 154 | } 155 | 156 | default: 157 | return state; 158 | } 159 | } 160 | 161 | export const Store = React.createContext(""); 162 | 163 | export function StoreProvider(props) { 164 | const [state, dispatch] = React.useReducer(reducer, initialState); 165 | const value = { state, dispatch }; 166 | return {props.children} 167 | } -------------------------------------------------------------------------------- /src/utils/buildENV.js: -------------------------------------------------------------------------------- 1 | import tabs from './tabs'; 2 | import { build } from 'protobufjs'; 3 | 4 | const buildENV = URI => { 5 | let envString = `DB_URI=${URI}`; 6 | return envString; 7 | } 8 | 9 | export default buildENV; 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/utils/buildExportTestSuite.js: -------------------------------------------------------------------------------- 1 | exports.createTest = function(queries, responses) { 2 | let tests = ''; 3 | for (let i=0; i 0) { 5 | //If this is the first entry add two empty lines. 6 | tests = tests.concat('\n\ntest(\'query\', () => {\n\texpect(\''+queries[i]+'\').toEqual(\''+responses[i]+'\')\n})'); 7 | } else { 8 | tests = tests.concat('test(\'query\', () => {\n\texpect(\''+queries[i]+'\').toEqual(\''+responses[i]+'\')\n})'); 9 | } 10 | } 11 | return tests; 12 | }; -------------------------------------------------------------------------------- /src/utils/buildGQLInputTypes.js: -------------------------------------------------------------------------------- 1 | import tabs from './tabs'; 2 | 3 | // Autogenerate default GQL types with object & scalar GQL fields 4 | const buildGQLInputTypes = tables => { 5 | let gqlTypes = ``; 6 | 7 | // Define a GQL Type for each table 8 | for (let tbIndex in tables) { 9 | const table = tables[tbIndex]; 10 | gqlTypes += `${tabs(1)}input ${table.type}Input {\n`; // Open GQL type definition 11 | // Iterate through each table field and define its respective GQL property 12 | for (let fieldIndex in table.fields) { 13 | const field = table.fields[fieldIndex]; 14 | gqlTypes += field.relationSelected ? addObjectInput(tables, field) : addScalarField(field); 15 | gqlTypes += `,\n`; 16 | } 17 | gqlTypes += `${tabs(1)}}\n\n`; // Close GQL type definition 18 | } 19 | 20 | return gqlTypes; 21 | } 22 | 23 | /********************************** HELPER FUNCTIONS **********************************/ 24 | 25 | // Returns a string of a GQL object field 26 | const addObjectInput = (tables, field) => { 27 | const { tableIndex, refType } = field.relation; 28 | const linkedTableName = tables[tableIndex].type; 29 | // Wrap linked field in curly braces if we have an 'xxx to many' relationship 30 | let objectField = refType.slice(-4) === `many` ? 31 | `${tabs(2)}${linkedTableName.toLowerCase()}: [${linkedTableName}Input]` : 32 | `${tabs(2)}${linkedTableName.toLowerCase()}: ${linkedTableName}Input`; 33 | return objectField; 34 | } 35 | 36 | // Returns a string of a GQL scalar field 37 | const addScalarField = field => { 38 | let scalarField = `${tabs(2)}${field.name}: ${field.type}`; 39 | scalarField += field.required ? `!` : ``; 40 | return scalarField; 41 | } 42 | 43 | export default buildGQLInputTypes; -------------------------------------------------------------------------------- /src/utils/buildGQLMutationTypes.js: -------------------------------------------------------------------------------- 1 | import tabs from './tabs'; 2 | 3 | // Autogenerate default GQL queries 4 | const buildGQLMutationTypes = tables => { 5 | let gqlMutation = `${tabs(1)}type Mutation {\n`; 6 | 7 | // Define a GQL mutation for each table 8 | for (let tbIndex in tables) { 9 | const table = tables[tbIndex]; 10 | gqlMutation += `${tabs(2)}add${table.type}(\n${tabs(3)}input: ${table.type}Input\n${tabs(2)}): [${table.type}]\n`; 11 | 12 | } 13 | 14 | gqlMutation += `${tabs(1)}}\n\n`; 15 | 16 | return gqlMutation; 17 | } 18 | 19 | export default buildGQLMutationTypes; -------------------------------------------------------------------------------- /src/utils/buildGQLObjTypes.js: -------------------------------------------------------------------------------- 1 | import tabs from './tabs'; 2 | 3 | // Autogenerate default GQL types with object & scalar GQL fields 4 | const buildGQLObjTypes = tables => { 5 | let gqlTypes = ``; 6 | 7 | // Define a GQL Type for each table 8 | for (let tbIndex in tables) { 9 | const table = tables[tbIndex]; 10 | gqlTypes += `${tabs(1)}type ${table.type} {\n`; // Open GQL type definition 11 | // Iterate through each table field and define its respective GQL property 12 | for (let fieldIndex in table.fields) { 13 | const field = table.fields[fieldIndex]; 14 | gqlTypes += field.relationSelected ? addObjectField(tables, field) : addScalarField(field); 15 | gqlTypes += `\n`; 16 | } 17 | gqlTypes += `${tabs(1)}}\n\n`; // Close GQL type definition 18 | } 19 | 20 | return gqlTypes; 21 | } 22 | 23 | /********************************** HELPER FUNCTIONS **********************************/ 24 | 25 | // Returns a string of a GQL object field 26 | const addObjectField = (tables, field) => { 27 | const { tableIndex, refType } = field.relation; 28 | const linkedTableName = tables[tableIndex].type; 29 | // Wrap linked field in curly braces if we have an 'xxx to many' relationship 30 | let objectField = refType.slice(-4) === `many` ? 31 | `${tabs(2)}${linkedTableName.toLowerCase()}: [${linkedTableName}]` : 32 | `${tabs(2)}${linkedTableName.toLowerCase()}: ${linkedTableName}`; 33 | return objectField; 34 | } 35 | 36 | // Returns a string of a GQL scalar field 37 | const addScalarField = field => { 38 | let scalarField = `${tabs(2)}${field.name}: ${field.type}`; 39 | scalarField += field.required ? `!` : ``; 40 | return scalarField; 41 | } 42 | 43 | export default buildGQLObjTypes; -------------------------------------------------------------------------------- /src/utils/buildGQLQueryType.js: -------------------------------------------------------------------------------- 1 | import tabs from './tabs'; 2 | 3 | // Autogenerate default GQL queries 4 | const buildGQLQueryType = tables => { 5 | let gqlQuery = `${tabs(1)}type Query {\n`; 6 | 7 | // Define a GQL query for each table 8 | for (let tbIndex in tables) { 9 | const table = tables[tbIndex]; 10 | gqlQuery += `${tabs(2)}getAll${table.type}: [${table.type}]\n`; 11 | 12 | // Check if table has at least one queryable field 13 | // and extract the last queryable field index from table 14 | let queryable = false; 15 | let lastQryFieldIndex = -1; 16 | for (let fieldIndex in table.fields) { 17 | const field = table.fields[fieldIndex]; 18 | if (field.queryable) { 19 | queryable = true; 20 | lastQryFieldIndex = Math.max(fieldIndex, lastQryFieldIndex); 21 | } 22 | } 23 | 24 | // If the table has at least one queryable field, then provide a query by that field 25 | if (queryable) { 26 | gqlQuery += `${tabs(2)}get${table.type}(\n`; 27 | for (let fieldIndex in table.fields) { 28 | const field = table.fields[fieldIndex]; 29 | // check if the field is queryable 30 | if (field.queryable) { 31 | gqlQuery += `${tabs(3)}${field.name}: ${field.type}`; 32 | if (lastQryFieldIndex !== Number(fieldIndex)) gqlQuery += ','; 33 | gqlQuery += `\n`; 34 | } 35 | } 36 | gqlQuery += `${tabs(2)}): [${table.type}]\n`; 37 | } 38 | } 39 | 40 | gqlQuery += `${tabs(1)}}\n`; 41 | 42 | return gqlQuery; 43 | } 44 | 45 | export default buildGQLQueryType; -------------------------------------------------------------------------------- /src/utils/buildGQLResolvers.js: -------------------------------------------------------------------------------- 1 | import tabs from './tabs'; 2 | 3 | const buildGQLResolvers = tables => { 4 | let gqlResolvers = `const pool = require('../db/sqlPool');\n\n`; 5 | gqlResolvers += `const resolvers = {\n\n`; 6 | 7 | // QUERY TYPE RESOLVERS 8 | gqlResolvers += `${tabs(1)}Query: {\n`; 9 | for (let tbIndex in tables) { 10 | const table = tables[tbIndex]; 11 | 12 | // handles getAll_______ Queries 13 | gqlResolvers += `${tabs(2)}getAll${table.type}() {\n`; 14 | gqlResolvers += `${tabs(3)}const sql = \`SELECT * FROM "${table.type}";\`;\n`; 15 | gqlResolvers += `${tabs(3)}return pool.query(sql)\n`; 16 | gqlResolvers += `${tabs(4)}.then(res => res.rows)\n`; 17 | gqlResolvers += `${tabs(4)}.catch(err => console.error('Error is: ', err));\n`; 18 | gqlResolvers += `${tabs(2)}},\n`; 19 | 20 | // Check if table has at least one queryable field 21 | // and extract the last queryable field index from table 22 | let queryable = false; 23 | let lastQryFieldIndex = -1; 24 | for (let fieldIndex in table.fields) { 25 | const field = table.fields[fieldIndex]; 26 | if (field.queryable) { 27 | queryable = true; 28 | lastQryFieldIndex = Math.max(fieldIndex, lastQryFieldIndex); 29 | } 30 | } 31 | 32 | // Custom Queries (get____(args: ...)) 33 | let customQryResolver = ``; 34 | if (queryable) { 35 | customQryResolver += `${tabs(2)}get${table.type}(parent, args, context, info) {\n`; 36 | customQryResolver += `${tabs(3)}let sql = \`SELECT * FROM "${table.type}"\`;\n`; 37 | customQryResolver += `${tabs(3)}let whereClause = \` WHERE \`;\n`; 38 | customQryResolver += `${tabs(3)}Object.keys(args).forEach((fieldName, i , arr) => {\n`; 39 | customQryResolver += `${tabs(4)}whereClause += \`"\${fieldName}" = '\${args[fieldName]}'\`;\n`; 40 | customQryResolver += `${tabs(4)}if (i !== arr.length - 1) whereClause += \` AND \`;\n`; 41 | customQryResolver += `${tabs(4)}else whereClause += \`;\`;\n`; 42 | customQryResolver += `${tabs(3)}});\n`; 43 | customQryResolver += `${tabs(3)}sql += whereClause;\n`; 44 | } 45 | 46 | customQryResolver += `${tabs(3)}return pool.query(sql)\n`; 47 | customQryResolver += `${tabs(4)}.then(res => res.rows)\n`; 48 | customQryResolver += `${tabs(4)}.catch(err => console.error('Error is: ', err));\n`; 49 | customQryResolver += `${tabs(2)}},\n`; 50 | 51 | // If the table has at least one queryable field, then provide a query by that field 52 | if (queryable) gqlResolvers += customQryResolver; 53 | } 54 | 55 | gqlResolvers += `${tabs(1)}},\n\n`; 56 | 57 | // OBJECT TYPE RESOLVERS 58 | for (let tbIndex in tables) { 59 | const table = tables[tbIndex]; 60 | gqlResolvers += `${tabs(1)}${table.type}: {\n`; 61 | for (let fieldIndex in table.fields) { 62 | const field = table.fields[fieldIndex]; 63 | // handle scalar fields 64 | if (!field.relationSelected) { 65 | gqlResolvers += `${tabs(2)}${field.name}: (parent, args, context, info) => {\n`; 66 | gqlResolvers += `${tabs(3)}return parent.${field.name};\n${tabs(2)}},\n`; 67 | } else { 68 | // handle non-scalar fields 69 | const linkedTable = tables[field.relation.tableIndex]; 70 | const linkedTableName = linkedTable.type; 71 | const linkedTableField = linkedTable.fields[field.relation.fieldIndex].name; 72 | gqlResolvers += `${tabs(2)}${linkedTableName.toLowerCase()}: (parent, args, context, info) => {\n`; 73 | gqlResolvers += `${tabs(3)}const sql = \`SELECT * FROM "${linkedTableName}" WHERE "${linkedTableField}" = '$\{parent.${field.name}}';\`;\n`; 74 | gqlResolvers += `${tabs(3)}return pool.query(sql)\n`; 75 | gqlResolvers += `${tabs(4)}.then(res => res.rows[0])\n`; 76 | gqlResolvers += `${tabs(4)}.catch(err => console.error('Error is: ', err))\n`; 77 | gqlResolvers += `${tabs(2)}},\n` 78 | } 79 | } 80 | 81 | gqlResolvers += `${tabs(1)}},\n\n`; 82 | 83 | } 84 | 85 | 86 | //MUTATION TYPE RESOLVERS 87 | gqlResolvers += `${tabs(1)}Mutation: {\n`; 88 | for (let tbIndex in tables) { 89 | const table = tables[tbIndex]; 90 | 91 | // handles add_______ Mutations 92 | gqlResolvers += `${tabs(2)}add${table.type}(parent, args, context, info) {\n`; 93 | gqlResolvers += `${tabs(3)}let sql = \`INSERT INTO "${table.type}" \`;\n`; 94 | gqlResolvers += `${tabs(3)}let valuesStr = \` VALUES (\`;\n`; 95 | gqlResolvers += `${tabs(3)}let values = [];\n`; 96 | gqlResolvers += `${tabs(3)}let columnsStr = \`(\`\n`; 97 | gqlResolvers += `${tabs(3)}Object.keys(args.input).forEach((fieldName, i, arr) => {\n`; 98 | gqlResolvers += `${tabs(4)}if(i == arr.length - 1){\n`; 99 | gqlResolvers += `${tabs(5)}columnsStr += \`\${fieldName})\`\n`; 100 | gqlResolvers += `${tabs(4)}} else {\n`; 101 | gqlResolvers += `${tabs(5)}columnsStr += \`\${fieldName},\`\n`; 102 | gqlResolvers += `${tabs(4)}}\n`; 103 | gqlResolvers += `${tabs(3)}})\n`; 104 | gqlResolvers += `${tabs(3)}Object.values(args.input).forEach((value, i, arr) => {\n`; 105 | gqlResolvers += `${tabs(4)}if( i == arr.length - 1){\n`; 106 | gqlResolvers += `${tabs(5)}valuesStr += \`$\${i + 1})\`\n`; 107 | gqlResolvers += `${tabs(4)}} else {\n`; 108 | gqlResolvers += `${tabs(5)}valuesStr += \`$\${i + 1},\`\n`; 109 | gqlResolvers += `${tabs(4)}}\n`; 110 | gqlResolvers += `${tabs(4)}values.push(value);\n`; 111 | gqlResolvers += `${tabs(3)}})\n`; 112 | gqlResolvers += `${tabs(3)}sql = sql + columnsStr + valuesStr;\n`; 113 | gqlResolvers += `${tabs(3)}return pool.query(sql, values)\n`; 114 | gqlResolvers += `${tabs(3)}.then(res => res.rows)\n`; 115 | gqlResolvers += `${tabs(3)}.catch(err => console.error('Error is: ', err));\n`; 116 | gqlResolvers += `${tabs(2)}},\n`; 117 | 118 | } 119 | 120 | gqlResolvers += `${tabs(1)}}\n`; 121 | gqlResolvers += `}\n\n`; 122 | 123 | gqlResolvers += `module.exports = resolvers;\n`; 124 | 125 | return gqlResolvers; 126 | } 127 | 128 | export default buildGQLResolvers; 129 | -------------------------------------------------------------------------------- /src/utils/buildGQLSchema.js: -------------------------------------------------------------------------------- 1 | import buildGQLObjTypes from './buildGQLObjTypes'; 2 | import buildGQLQueryType from './buildGQLQueryType'; 3 | import buildGQLInputTypes from './buildGQLInputTypes'; 4 | import buildGQLMutationTypes from './buildGQLMutationTypes'; 5 | 6 | // Build GQL Schema code 7 | const buildGQLSchema = tables => { 8 | // User must npm install 'apollo-server' or 'apollo-server-express' 9 | const requireApolloServer = `const { gql } = require('apollo-server-express');\n\n`; 10 | const typeDefs = `const typeDefs = gql\`\n\n${buildGQLObjTypes(tables)}${buildGQLInputTypes(tables)}${buildGQLMutationTypes(tables)}${buildGQLQueryType(tables)}\n\`;\n\n` 11 | const moduleExports = `module.exports = typeDefs;\n` 12 | return requireApolloServer + typeDefs + moduleExports; 13 | } 14 | 15 | export default buildGQLSchema; 16 | -------------------------------------------------------------------------------- /src/utils/buildSQLScripts.js: -------------------------------------------------------------------------------- 1 | import tabs from './tabs'; 2 | 3 | const showFields = fields => { 4 | const totalFields = Math.max(...Object.keys(fields)); 5 | let output = ``; 6 | 7 | // Define each field in SQL 8 | for (let i in fields) { 9 | const field = fields[i]; 10 | output += `${tabs(1)}"${field.name}"`; 11 | 12 | let sqlType = ''; 13 | switch(field.type) { 14 | case `ID`: 15 | sqlType += ` SERIAL`; 16 | break; 17 | case `String`: 18 | sqlType += ` VARCHAR(256)`; 19 | break; 20 | case `Int`: 21 | sqlType += ` INTEGER`; 22 | break; 23 | case `Float`: 24 | sqlType += ` FLOAT(8)`; 25 | break; 26 | case `Boolean`: 27 | sqlType += ` BOOLEAN`; 28 | break; 29 | default: 30 | sqlType += field.type; 31 | } 32 | 33 | output += sqlType; 34 | output += field.primaryKey ? ` PRIMARY KEY` : ``; 35 | output += field.unique ? ` UNIQUE` : ``; 36 | output += field.required ? ` NOT NULL` : ``; 37 | output += field.defaultValue ? ` DEFAULT '${field.defaultValue}'` : ``; 38 | output += i < totalFields ? `,\n` : `\n);\n\n`; 39 | } 40 | 41 | return output; 42 | } 43 | 44 | const buildSQLScripts = tableState => { 45 | const tables = Object.values(tableState); 46 | let sqlScripts = ``; 47 | 48 | tables.forEach(table => { 49 | sqlScripts += `CREATE TABLE "${table.type}" (\n`; 50 | sqlScripts += showFields(table.fields); 51 | }); 52 | 53 | return sqlScripts; 54 | } 55 | 56 | export default buildSQLScripts; 57 | -------------------------------------------------------------------------------- /src/utils/buildVisualizerJson.js: -------------------------------------------------------------------------------- 1 | 2 | /********************************** HELPER FUNCTIONS **********************************/ 3 | 4 | 5 | // Checks all fields of a table and returns an array of queryable fields 6 | export const queryableFieldsArrayCreator = fields => { 7 | const queryFieldsArray = []; 8 | for(let x in fields) { 9 | if (fields[x].queryable) { 10 | queryFieldsArray.push(fields[x]) 11 | } 12 | } 13 | return queryFieldsArray 14 | } 15 | 16 | //Check all fields of a table and return objects with the fields name and type 17 | export const fieldNameType = fields => { 18 | let fieldJson =`` 19 | for(let i = 0; i < fields.length; i++) { 20 | fieldJson += `{"name":"${fields[i].name}", "type":"${fields[i].type}"}` 21 | if (i !== fields.length - 1) { 22 | fieldJson += `,` 23 | } 24 | } 25 | return fieldJson; 26 | 27 | } 28 | 29 | export const queryableTableMaker = tables => { 30 | if (Object.keys(tables).length === 0) { 31 | console.log("tables is empty") 32 | return [] 33 | } 34 | console.log('queryableTableMaker tables:', tables) 35 | const tableArray = Object.values(tables) 36 | const queryableTables = [] 37 | tableArray.forEach( el => { 38 | //get list of fields that are queryable for current table ("el") 39 | const queryableFields = queryableFieldsArrayCreator(el.fields) 40 | 41 | /*if there is at least one queryable field in the table, update the tables fields to only the queryable fields and push to queryableTableArray */ 42 | if (queryableFields.length !== 0) { 43 | el.fields = queryableFields 44 | queryableTables.push(el) 45 | } 46 | }) 47 | return queryableTables 48 | } 49 | 50 | export function queryTypeCreator (tables) { 51 | const queryableTables = queryableTableMaker(tables) 52 | console.log("queryTypeCreator Tables:", tables) 53 | const typeArray = []; 54 | for (let i in queryableTables) { 55 | const curr = queryableTables[i] 56 | const fields = curr.fields 57 | const type = {} 58 | type.name = curr.type 59 | type.fields = {} 60 | for (let j in fields) { 61 | if (fields[j].relationSelected) { 62 | type.fields[tables[fields[j].relation.tableIndex].type] = `[${tables[fields[j].relation.tableIndex].type}]` 63 | } 64 | type.fields 65 | if (!fields[j].relationSelected) { 66 | type.fields[fields[j].name] = fields[j].type 67 | } 68 | } 69 | typeArray.push(type) 70 | } 71 | return typeArray 72 | } 73 | 74 | /********************************** BUILD JSON FOR VISUALIZER **********************************/ 75 | 76 | export const buildVisualizerJson = tables => { 77 | let root = `{"name":"Queries"` 78 | console.log('buildvisualizerjson tables: ', tables) 79 | 80 | //if we have no tables return only root 81 | if (Object.keys(tables).length === 0) return JSON.parse(root +='}') 82 | 83 | const queryableTableArray = queryableTableMaker(tables) 84 | 85 | //If there are no queryable fields return root 86 | if (queryableTableArray.length === 0) return JSON.parse(root +='}') 87 | 88 | //Building children of root query 89 | root += `, "children":[` 90 | for (let i = 0; i < queryableTableArray.length; i++) { 91 | //get All query 92 | root += `{"name":"getAll${queryableTableArray[i].type}","type":"[${queryableTableArray[i].type}]","children":[{"name":"${queryableTableArray[i].type}","type":"[${queryableTableArray[i].type}]"}]},` 93 | 94 | //get query 95 | root += `{"name": "get${queryableTableArray[i].type}","type":"[${queryableTableArray[i].type}]","children":[{"name": "${queryableTableArray[i].type}","type": "[${queryableTableArray[i].type}]","children": [${fieldNameType(queryableTableArray[i].fields)}]}]}` 96 | 97 | if (i !== queryableTableArray.length - 1) root += `,` 98 | } 99 | root += `]}` 100 | return JSON.parse(root) 101 | } -------------------------------------------------------------------------------- /src/utils/deepClone.js: -------------------------------------------------------------------------------- 1 | const deepClone = (obj) => JSON.parse(JSON.stringify(obj)); 2 | 3 | export default deepClone; -------------------------------------------------------------------------------- /src/utils/tabs.js: -------------------------------------------------------------------------------- 1 | // Returns string of user-input tabbed spaces to indent our code 2 | const tabs = numTabSpaces => { 3 | let tabSpaces = ``; 4 | while (numTabSpaces > 0) { 5 | tabSpaces += ` `; 6 | numTabSpaces--; 7 | } 8 | return tabSpaces; 9 | } 10 | 11 | export default tabs; -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/protographql/fcb163f037d5ba41a4edcd56dbae35f6ef41a835/tests/tests.js -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./public/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | "module": "commonjs", 7 | "target": "es6", 8 | "jsx": "react" 9 | }, 10 | "include": [ 11 | "./src/", "_protographql_tests_/utils.test.js", "_protographql_tests_/buildGQLSchema.test.js", "_protographql_tests_/buildENV.test.js" 12 | ] 13 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: ["@babel/polyfill","./src/index.jsx"], 5 | target: 'electron-main', 6 | output: { 7 | publicPath: '/public/', 8 | path: path.resolve(__dirname, "public"), 9 | filename: "bundle.js" 10 | }, 11 | 12 | devServer: { 13 | inline: true, 14 | port: 8082 15 | }, 16 | 17 | // Enable sourcemaps for debugging webpack's output. 18 | devtool: "source-map", 19 | 20 | resolve: { 21 | // Add '.ts' and '.tsx' as resolvable extensions. 22 | extensions: [".ts", ".tsx", ".mjs", ".js", ".jsx", ".json"] 23 | }, 24 | 25 | module: { 26 | rules: [ 27 | // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. 28 | { 29 | test: /\.tsx?$/, 30 | exclude: /node_modules/, 31 | loader: "awesome-typescript-loader" 32 | }, 33 | 34 | { 35 | test: /jsx?$/, 36 | exclude: /node_modules/, 37 | loader: 'babel-loader', 38 | query: { 39 | presets: ['@babel/env', '@babel/react'], 40 | } 41 | }, 42 | 43 | { 44 | test:/\.css$/, 45 | exclude: /node_modules/, 46 | use: ['style-loader', 'css-loader'] 47 | }, 48 | 49 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 50 | { 51 | enforce: "pre", 52 | test: /\.js$/, 53 | exclude: /node_modules/, 54 | loader: "source-map-loader" 55 | }, 56 | ] 57 | } 58 | }; --------------------------------------------------------------------------------