├── .babelrc ├── .gitignore ├── README.md ├── __tests__ ├── components.test.js ├── gitignore.test.js ├── server.test.js └── sum.test.js ├── babel.config.js ├── build ├── d594cc6265f15715be3e.png ├── preload.js └── style.css ├── icon.ico ├── icon.png ├── icon_dark.png ├── index.html ├── jest-setup.js ├── jest-teardown.js ├── jest.config.js ├── license.txt ├── mac-package.json ├── main.tsx ├── menu_bar.png ├── models ├── dbRetriver.js └── injection.js ├── outputer.txt ├── p.png ├── package.json ├── remoteserver └── package.txt ├── server ├── GQLfactory │ ├── schema.js │ └── types.js ├── controller │ └── GQLcontroller.js └── server.js ├── splash.html ├── src ├── App.tsx ├── components │ ├── Card.tsx │ ├── CodeMirror.tsx │ ├── Darkmode.tsx │ ├── GraphQLSidebar.tsx │ ├── Navbar.tsx │ ├── SandBox.tsx │ ├── UriMenu.tsx │ └── Visualizer.tsx ├── containers │ ├── MainContainer.tsx │ └── VisualContainer.tsx ├── index.tsx └── style │ ├── _variables.scss │ ├── app.scss │ ├── darkmode.scss │ ├── navbar.scss │ ├── sidebar.scss │ └── visualizer.scss ├── sum.js ├── tsconfig.json ├── webpack.config.ts └── workspace.code-workspace /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/bundle.js 3 | .env 4 | package-lock.json 5 | out/ 6 | dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QLeksii 2 | 3 | ## What does QLeksii do? 4 | 5 | QLeksii is a Graphical User Interface (GUI) designed for GraphQL to help engineers by generating different types of queries based on the collections and tables from the database URL provided by the user. The utilization of GraphQL technology provides the user with a more granular and more efficient way of accessing database information. QLeksii will provide all the information extracted from the database and dynamically generate different query types based on that information for the user. This process eliminates the need to create queries manually, which means less time consumption, and reduced costs. 6 | 7 | # How does it work? 8 | 9 | ## Installation Instructions 10 | 1. Clone the repo 11 | ```sh 12 | git clone https://github.com/github_username/repo_name.git 13 | ``` 14 | 2. Install NPM packages 15 | ```sh 16 | npm install 17 | ``` 18 | 3. Run Application in Terminal 19 | ```sh 20 | npm start 21 | ``` 22 | 23 | ## Accessing the database 24 | 25 | Once the user provides the database URL, the application will handle all the work for you. It will first mutate the link provided into something the server recognizes, then the server will do its job to retrieve all the information from the database. Once all the information is extracted, the application will dynamically create cards for each table in the database including the type of data of each entry, which will be shown in the application. 26 | 27 | ![](https://miro.medium.com/max/700/1*4Drwq7uybBSxRvShjYoepA.gif) 28 | 29 | ## Generating different types of queries 30 | 31 | By clicking on the button on the upper right corner the user will access the sidebar which stores different types of queries. The user will have the option to select their preferred type of query, and the application will show all the generated queries based on the user input and selection. 32 | 33 | ![](https://miro.medium.com/max/700/1*m9capS_CLBPqD8N9_1YOtA.gif) 34 | 35 | ## Accessing GraphiQL via Sandbox button 36 | 37 | Alongside the tabs for the queries, the user can also use a "sandbox", where it will pop up a new window for the user to utilize the queries generated by the application, using the GraphiQL interface. 38 | 39 | ![](https://miro.medium.com/max/700/1*JkbWxDGU2t0ZRMqLgGAZrw.gif) 40 | 41 | ## Current Features: 42 | 43 | - Visualization of the collections and tables extracted from the database, including their data types 44 | - Dynamically generate different types queries based on user preference 45 | - Allows the user to utilize the generated queries by providing a "sandbox", where the user can freely interact with the database 46 | 47 | ## Additional Features to be added: 48 | 49 | - Integration with SQL Databases 50 | - Export generated GraphQL queries for the user to use as they wish 51 | 52 | ## Contributors: 53 | - [Damien Evans](https://github.com/dnyceboy) 54 | - [Dennis Palomo](https://github.com/dennispalomo) 55 | - [Hasan H Ozutemiz](https://github.com/hozutemiz) 56 | - [Oleksii Hordiienko](https://github.com/alexr1der) 57 | - [Storm Ross](https://github.com/sar516) 58 | -------------------------------------------------------------------------------- /__tests__/components.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { configure, shallow } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | 6 | import { MainContainer } from '../src/containers/MainContainer'; 7 | import { VisualContainer } from '../src/containers/VisualContainer'; 8 | import { Navbar } from '../src/components/Navbar'; 9 | 10 | // Newer Enzyme versions require an adapter to a particular version of React 11 | configure({ adapter: new Adapter() }); 12 | 13 | describe('React Unit Tests', () => { 14 | 15 | describe('Navbar Unit Tests', () => { 16 | 17 | let wrapper; 18 | const props = { 19 | onMenuToggle: false, 20 | } 21 | 22 | beforeAll(() => { 23 | wrapper = shallow(); 24 | }); 25 | 26 | it('Renders div', () => { 27 | expect(wrapper.type()).toEqual('div'); 28 | }); 29 | 30 | it('Div has class of navbar', () => { 31 | expect(wrapper.hasClass('Navbar')).toEqual(true); 32 | }); 33 | 34 | it('Div has unordered list', () => { 35 | expect(wrapper.find('ul')).toHaveLength(1); 36 | }); 37 | 38 | }); 39 | 40 | describe('Visual Container Unit Tests', () => { 41 | 42 | let wrapper; 43 | const props = { 44 | tables: ['doctor', 'patients', 'nurses'], 45 | fields: {firstName: true, lastName: true}, 46 | uri: 'mongodb+srv://', 47 | } 48 | 49 | beforeAll(() => { 50 | wrapper = shallow(); 51 | }); 52 | 53 | it('Renders div', () => { 54 | expect(wrapper.type()).toEqual('div'); 55 | }); 56 | 57 | it('Tests if visual container has class of VisualContainer', () => { 58 | expect(wrapper.hasClass('VisualContainer')).toEqual(true); 59 | }); 60 | 61 | it('Tests if visual container has class of container', () => { 62 | expect(wrapper.find('.container').exists()).toEqual(true); 63 | }); 64 | 65 | it('Tests if Visualizer Exists', () => { 66 | expect(wrapper.find('Visualizer').exists()).toEqual(true); 67 | }); 68 | 69 | 70 | }) 71 | 72 | }) -------------------------------------------------------------------------------- /__tests__/gitignore.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const gitignore = path.resolve(__dirname, '../.gitignore'); 5 | 6 | describe('Gitignore Tests', () => { 7 | 8 | it('Testing if node_modules is included in the file', () => { 9 | fs.readFile(gitignore, function(err, data){ 10 | if(err) throw err; 11 | const result = data.includes("node_modules"); 12 | expect(result).toEqual(true); 13 | }); 14 | }) 15 | 16 | it('Testing if .env is included in the file', () => { 17 | fs.readFile(gitignore, function(err, data){ 18 | if(err) throw err; 19 | const result = data.includes(".env"); 20 | expect(result).toEqual(true); 21 | }); 22 | }) 23 | 24 | it('Testing if package-lock.json is included in the file', () => { 25 | fs.readFile(gitignore, function(err, data){ 26 | if(err) throw err; 27 | const result = data.includes("package-lock.json"); 28 | expect(result).toEqual(true); 29 | }); 30 | }) 31 | 32 | it('Testing if build/bundle.js is included in the file', () => { 33 | fs.readFile(gitignore, function(err, data){ 34 | if(err) throw err; 35 | const result = data.includes("build/bundle.js"); 36 | expect(result).toEqual(true); 37 | }); 38 | }) 39 | 40 | it('Testing if out/ is included in the file', () => { 41 | fs.readFile(gitignore, function(err, data){ 42 | if(err) throw err; 43 | const result = data.includes("out/"); 44 | expect(result).toEqual(true); 45 | }); 46 | }) 47 | 48 | it('Testing if dist is included in the file', () => { 49 | fs.readFile(gitignore, function(err, data){ 50 | if(err) throw err; 51 | const result = data.includes("dist"); 52 | expect(result).toEqual(true); 53 | }); 54 | }) 55 | 56 | }); -------------------------------------------------------------------------------- /__tests__/server.test.js: -------------------------------------------------------------------------------- 1 | const GQLController = require('../server/controller/GQLcontroller'); 2 | const { main, linkparser, listDatabases, gimeData } = require('../models/dbRetriver'); 3 | const {MongoClient} = require('mongodb'); 4 | const mongo_url='mongodb+srv://alex:alex@cluster0.q9ag7.mongodb.net/myFirstDatabase?retryWrites=true&w=majority'; 5 | const request = require('supertest'); 6 | const express = require('express'); 7 | const app = express(); 8 | const server = 'http://localhost:3333'; 9 | import "babel-polyfill" 10 | import 'regenerator-runtime/runtime'; 11 | 12 | // const server = 'http://localhost:3333'; 13 | 14 | // const app = require('./server') // Link to your server file 15 | // const supertest = require('supertest') 16 | // const request = supertest(app) 17 | 18 | 19 | describe('database connection', () => { 20 | let connection; 21 | let db; 22 | 23 | beforeAll(async () => { 24 | connection = await MongoClient.connect(mongo_url, { 25 | useNewUrlParser: true, 26 | }); 27 | db = await connection.db("new_db"); 28 | }); 29 | 30 | afterAll(async () => { 31 | await connection.close(); 32 | // await db.close(); 33 | }); 34 | 35 | 36 | it('should insert a doc into collection', async () => { 37 | const users = db.collection('tasks'); 38 | const randomNumber = Math.floor(Math.random() * 10000000); 39 | const mockUser = {_id: randomNumber, item: `test ${randomNumber}`}; 40 | await users.insertOne(mockUser); 41 | 42 | const insertedUser = await users.findOne({_id: randomNumber}); 43 | expect(insertedUser).toEqual(mockUser); 44 | }); 45 | 46 | }); 47 | 48 | // describe('Server Unit Tests', () => { 49 | 50 | 51 | // it('Testing post request for root endpoint', () => { 52 | // return request(app) 53 | // .post('/') 54 | // .send({URI: mongo_url}) 55 | // .expect('Content-Type', /text\/html/) 56 | // .expect(200) 57 | // }); 58 | 59 | // }); 60 | 61 | -------------------------------------------------------------------------------- /__tests__/sum.test.js: -------------------------------------------------------------------------------- 1 | const sum = require('../sum'); 2 | 3 | test('adds 1 + 2 to equal 3', () => { 4 | expect(sum(1, 2)).toBe(3); 5 | }); -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {presets: ["@babel/preset-env", "@babel/preset-react"]} -------------------------------------------------------------------------------- /build/d594cc6265f15715be3e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLeksii/2d6e4b136e36bb09cb19341579e3c28cb7fc48d3/build/d594cc6265f15715be3e.png -------------------------------------------------------------------------------- /build/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron'); 2 | var exec = require('child_process').exec; 3 | 4 | var cmd = 'node ./server/server.js'; 5 | // runs the server, default theme, and tests upon startup 6 | contextBridge.exposeInMainWorld('darkMode', { 7 | toggle: () => ipcRenderer.invoke('dark-mode:toggle'), 8 | system: () => ipcRenderer.invoke('dark-mode:system'), 9 | test: () => ipcRenderer.invoke('test'), 10 | switch: () => ipcRenderer.invoke('switch'), 11 | }); 12 | 13 | exec(cmd, function (error, stdout, stderr) { 14 | console.log('done'); 15 | }); 16 | -------------------------------------------------------------------------------- /build/style.css: -------------------------------------------------------------------------------- 1 | h1{ 2 | color: aqua; 3 | background-color: blue; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLeksii/2d6e4b136e36bb09cb19341579e3c28cb7fc48d3/icon.ico -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLeksii/2d6e4b136e36bb09cb19341579e3c28cb7fc48d3/icon.png -------------------------------------------------------------------------------- /icon_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLeksii/2d6e4b136e36bb09cb19341579e3c28cb7fc48d3/icon_dark.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /jest-setup.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | global.testServer = await require('./server/server'); 3 | }; -------------------------------------------------------------------------------- /jest-teardown.js: -------------------------------------------------------------------------------- 1 | module.exports = async (globalConfig) => { 2 | testServer.close(); 3 | }; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | transform: { 4 | '^.+\\.(ts|tsx)?$': 'ts-jest', 5 | "^.+\\.(js|jsx)$": "babel-jest", 6 | } 7 | }; -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | This application is licensed and owned by Qleksii. Click the "I Accept" button below to accept our terms and conditions. Shout Out the bananas out there... Fergie was right... -------------------------------------------------------------------------------- /mac-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qleksii", 3 | "productName": "Qleksii", 4 | "version": "1.0.0", 5 | "description": "Electron GraphQL tool for delivering you all your GraphQL needs", 6 | "main": "main.tsx", 7 | "scripts": { 8 | "test": "jest --verbose", 9 | "start": "electron-forge start", 10 | "pre": "node ./server/server.js", 11 | "watch": "webpack --config webpack.config.ts --watch", 12 | "package": "electron-forge package", 13 | "make": "electron-forge make", 14 | "build-installer": "electron-builder -m", 15 | "electron": "electron ." 16 | }, 17 | "build": { 18 | "appId": "qleksii-app", 19 | "win": { 20 | "target": [ 21 | "nsis" 22 | ], 23 | "icon": "icon.ico" 24 | }, 25 | "dmg": { 26 | "background": null, 27 | "backgroundColor": "#ffffff", 28 | "window": { 29 | "width": "400", 30 | "height": "300" 31 | }, 32 | "contents": [ 33 | { 34 | "x": 100, 35 | "y": 100 36 | }, 37 | { 38 | "x": 300, 39 | "y": 100, 40 | "type": "link", 41 | "path": "/Applications" 42 | } 43 | ] 44 | }, 45 | "mac": { 46 | "target": "dmg", 47 | "category": "public.app-category.utilities" 48 | }, 49 | "nsis": { 50 | "oneClick": false, 51 | "license": "license.txt", 52 | "allowToChangeInstallationDirectory": true 53 | } 54 | }, 55 | "keywords": [], 56 | "author": "Damien, Storm, Hasan, Oleksii, Dennis", 57 | "license": "ISC", 58 | "jest": { 59 | "globalSetup": "./jest-setup.js", 60 | "globalTeardown": "./jest-teardown.js" 61 | }, 62 | "dependencies": { 63 | "@babel/core": "^7.15.0", 64 | "@shelf/jest-mongodb": "^2.0.3", 65 | "@types/jest": "^27.0.1", 66 | "body-parser": "^1.19.0", 67 | "codemirror": "^5.62.3", 68 | "cors": "^2.8.5", 69 | "css-loader": "^6.2.0", 70 | "dotenv": "^10.0.0", 71 | "electron-squirrel-startup": "^1.0.0", 72 | "enzyme": "^3.11.0", 73 | "enzyme-adapter-react-16": "^1.15.6", 74 | "enzyme-to-json": "^3.6.2", 75 | "express": "^4.17.1", 76 | "express-graphql": "^0.12.0", 77 | "fs": "0.0.1-security", 78 | "graphql": "^15.5.1", 79 | "mongodb": "^4.1.0", 80 | "mongoose": "^6.0.5", 81 | "path": "^0.12.7", 82 | "react": "^16.9.2", 83 | "react-codemirror": "^1.0.0", 84 | "react-codemirror2": "^7.2.1", 85 | "react-dom": "^17.0.2", 86 | "sass": "^1.37.5", 87 | "sass-loader": "^12.1.0", 88 | "save-dev": "0.0.1-security", 89 | "style-loader": "^3.2.1", 90 | "ts-jest": "^27.0.5", 91 | "webpack": "^5.50.0", 92 | "webpack-cli": "^4.7.2" 93 | }, 94 | "devDependencies": { 95 | "@babel/preset-env": "^7.15.0", 96 | "@babel/preset-react": "^7.14.5", 97 | "@babel/preset-typescript": "^7.15.0", 98 | "@electron-forge/cli": "^6.0.0-beta.59", 99 | "@electron-forge/maker-deb": "^6.0.0-beta.59", 100 | "@electron-forge/maker-rpm": "^6.0.0-beta.59", 101 | "@electron-forge/maker-squirrel": "^6.0.0-beta.59", 102 | "@electron-forge/maker-zip": "^6.0.0-beta.59", 103 | "@types/react": "^17.0.18", 104 | "@types/react-dom": "^17.0.9", 105 | "@types/webpack-dev-server": "^3.11.5", 106 | "babel-core": "^6.26.3", 107 | "babel-jest": "^27.1.0", 108 | "babel-loader": "^8.2.2", 109 | "babel-polyfill": "^6.26.0", 110 | "babel-preset-es2015": "^6.24.1", 111 | "babel-preset-stage-0": "^6.24.1", 112 | "electron": "^13.2.2", 113 | "electron-builder": "^22.11.7", 114 | "electron-reload": "^2.0.0-alpha.1", 115 | "jest": "^27.1.0", 116 | "react-test-renderer": "^17.0.2", 117 | "supertest": "^6.1.6", 118 | "ts-node": "^10.2.0", 119 | "typescript": "^4.3.5", 120 | "webpack-dev-server": "^3.11.2" 121 | }, 122 | "config": { 123 | "forge": { 124 | "packagerConfig": {}, 125 | "makers": [ 126 | { 127 | "name": "@electron-forge/maker-squirrel", 128 | "config": { 129 | "name": "qleksii" 130 | } 131 | }, 132 | { 133 | "name": "@electron-forge/maker-zip", 134 | "platforms": [ 135 | "darwin" 136 | ] 137 | }, 138 | { 139 | "name": "@electron-forge/maker-deb", 140 | "config": {} 141 | }, 142 | { 143 | "name": "@electron-forge/maker-rpm", 144 | "config": {} 145 | } 146 | ] 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /main.tsx: -------------------------------------------------------------------------------- 1 | const { 2 | app, 3 | BrowserWindow, 4 | ipcMain, 5 | nativeTheme, 6 | globalShortcut, 7 | } = require('electron'); 8 | const path = require('path'); 9 | const fs = require('fs'); 10 | var exec = require('child_process').exec; 11 | 12 | // creates the window for URI input for electron app 13 | function createWindow(params) { 14 | const win = new BrowserWindow({ 15 | width: 1200, 16 | height: 800, 17 | icon: __dirname + '/icon.png', 18 | autoHideMenuBar: true, 19 | webPreferences: { 20 | preload: path.join(__dirname, './build/preload.js'), 21 | nodeIntegration: true, 22 | worldSafeExecuteJavaScript: true, 23 | contextIsolation: true, 24 | }, 25 | }); 26 | globalShortcut.register('Alt+CommandOrControl+I', () => { 27 | // console.log('Electron loves global shortcuts!'); 28 | win.webContents.toggleDevTools(); 29 | }); 30 | // creates a child window for the graphql path 31 | const child = new BrowserWindow({ 32 | width: 1200, 33 | height: 800, 34 | icon: __dirname + '/icon.png', 35 | autoHideMenuBar: true, 36 | parent: win, 37 | }); 38 | 39 | child.hide(); 40 | win.loadFile('index.html'); 41 | 42 | // provides the dark mode functionality, theme is light by default 43 | ipcMain.handle('dark-mode:toggle', () => { 44 | if (nativeTheme.shouldUseDarkColors) { 45 | nativeTheme.themeSource = 'light'; 46 | } else { 47 | nativeTheme.themeSource = 'dark'; 48 | } 49 | return nativeTheme.shouldUseDarkColors; 50 | }); 51 | 52 | // starts up the remote server 53 | ipcMain.handle('switch', async () => { 54 | var cmd2 = 'node ./remoteserver/server.js'; 55 | await exec(cmd2, function (error, stdout, stderr) { 56 | console.log('stdout ', stdout); 57 | console.log(stderr); 58 | }); 59 | // loads the page from the graphql path on the child window 60 | setTimeout(() => { 61 | console.log('loading url'); 62 | child.loadURL('http://localhost:3200/graphql'); 63 | child.show(); 64 | }, 500); 65 | }); 66 | // closes child window and kills the port used for graphiql 67 | child.on('close', function (event) { 68 | event.preventDefault(); 69 | var cmd3 = 'npx kill-port 3200'; 70 | exec(cmd3, function (error, stdout, stderr) { 71 | child.hide(); 72 | }); 73 | }); 74 | 75 | // closes main window and kills the port used for remote server 76 | win.on('closed', async function (event) { 77 | await exec('npx kill-port 3333', function (error, stdout, stderr) { 78 | console.log(stdout); 79 | }); 80 | if (fs.existsSync('./remoteserver/server.js')) { 81 | fs.unlink('./remoteserver/server.js', function (err) { 82 | if (err) { 83 | console.error(err); 84 | } else { 85 | console.log('File removed:', './remoteserver/server.js'); 86 | } 87 | }); 88 | } 89 | }); 90 | } 91 | 92 | app.whenReady().then(createWindow); 93 | -------------------------------------------------------------------------------- /menu_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLeksii/2d6e4b136e36bb09cb19341579e3c28cb7fc48d3/menu_bar.png -------------------------------------------------------------------------------- /models/dbRetriver.js: -------------------------------------------------------------------------------- 1 | const MongoClient = require('mongodb').MongoClient; 2 | const dovenv = require('dotenv').config(); 3 | const path = require('path'); 4 | const MONGO_URL = process.env.mongo_url; 5 | const fs = require('fs'); 6 | // modifies the link to be sent to the server 7 | function linkparser(link) { 8 | const start = link.indexOf('net/'); 9 | const end = link.indexOf('?'); 10 | const res = link.slice(Number(start) + 4, end); 11 | return res; 12 | } 13 | // creates an array of database list to be rendered 14 | async function listDatabases(client, dbName) { 15 | const output = []; 16 | databasesList = await client.db(dbName).listCollections({}).toArray(); 17 | databasesList.forEach((l) => output.push(l.name)); 18 | return output; 19 | } 20 | // retrieves the collections and tables from the database, and provides the type of value for each tables 21 | async function retrieveData(client, result, dbName) { 22 | const outputArr = []; 23 | 24 | let obj = {}; 25 | for (let i = 0; i < result.length; i++) { 26 | databasesList = await client.db(dbName).collection(result[i]).findOne({}); 27 | outputArr.push(databasesList); 28 | } 29 | for (let i = 0; i < outputArr.length; i++) { 30 | for (el in outputArr[i]) { 31 | if (!Array.isArray(outputArr[i][el])) { 32 | const tester = JSON.stringify(outputArr[i][el]); 33 | if (tester.slice(1, 3) === '5d' && el !== '_id') { 34 | obj[el] = result[i]; 35 | } 36 | } 37 | outputArr[i][el] = typeof outputArr[i][el]; 38 | } 39 | } 40 | return outputArr; 41 | } 42 | // provides the collections and tables from the database, and use the function gimeData to sort through the result 43 | async function main(req, res, next) { 44 | const client = new MongoClient(req.body.URI); 45 | try { 46 | await client.connect(); 47 | const db_hook_name = await linkparser(req.body.URI); 48 | const result = await listDatabases(client, db_hook_name); 49 | res.locals.db_tables = result; 50 | res.locals.db_data = await retrieveData(client, result, db_hook_name); 51 | return next(); 52 | } catch (e) { 53 | } finally { 54 | await client.close(); 55 | } 56 | } 57 | 58 | module.exports = { main, linkparser, listDatabases, retrieveData }; 59 | -------------------------------------------------------------------------------- /models/injection.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const console = require('console'); 3 | const MongoClient = require('mongodb').MongoClient; 4 | const schemaFactory = require('../server/GQLfactory/schema.js'); 5 | const type = require('../server/GQLfactory/types.js'); 6 | const fs = require('fs'); 7 | 8 | // modifies the input URI to be sent to the server 9 | function linkparser(link) { 10 | const start = link.indexOf('net/'); 11 | const end = link.indexOf('?'); 12 | const res = link.slice(Number(start) + 4, end); 13 | return res; 14 | } 15 | 16 | const top = `const express = require('express'); 17 | const path = require('path'); 18 | const PORT = 3200; 19 | const cors = require('cors'); 20 | const bodyParser = require('body-parser'); 21 | const { graphqlHTTP } = require('express-graphql'); 22 | const graphql = require('graphql'); 23 | const MongoClient = require('mongodb').MongoClient; 24 | const app = express(); 25 | let textsT; 26 | let conn; 27 | const mongoose = require('mongoose'); 28 | const {GraphQLObjectType, GraphQLList, GraphQLString, GraphQLSchema} = graphql;`; 29 | /* 30 | top var is here and always be the same string of things that we 31 | would need absolutely every time for server file that being generated 32 | */ 33 | 34 | function main(dbName, link) { 35 | str = ` 36 | async function main(){ 37 | try { 38 | await mongoose.connect("${link}", 39 | { useNewUrlParser: true, useUnifiedTopology: true, dbName: "${dbName}"}) 40 | console.log("DB_connection is stable"); 41 | conn = await mongoose.connection.db; 42 | 43 | } catch (e) { 44 | console.error(e); 45 | } 46 | }`; 47 | 48 | return str; 49 | } 50 | /* 51 | main function in a new server file is needed to establish a connection within mongoose methods to DB 52 | */ 53 | 54 | async function listDatabases(client, dbName) { 55 | const output = []; 56 | databasesList = await client.db(dbName).listCollections({}).toArray(); 57 | databasesList.forEach((l) => output.push(l.name)); 58 | return output; 59 | } 60 | async function retrieveData(client, result, dbName) { 61 | const outputArr = []; 62 | 63 | let obj = {}; 64 | for (let i = 0; i < result.length; i++) { 65 | databasesList = await client.db(dbName).collection(result[i]).findOne({}); 66 | outputArr.push(databasesList); 67 | } 68 | for (let i = 0; i < outputArr.length; i++) { 69 | // console.log(outputArr[i]) 70 | for (el in outputArr[i]) { 71 | if (!Array.isArray(outputArr[i][el])) { 72 | outputArr[i][el] = typeof outputArr[i][el]; 73 | } 74 | // console.log(obj); 75 | } 76 | } 77 | return outputArr; 78 | } 79 | /* 80 | listDatabases and gimeData accordingly gives the user an array of all collection names as an array and 81 | gimeData creates a data dump from all fields and types from each collection 82 | */ 83 | //.collection('texts') 84 | const mainR = ` 85 | main(); 86 | `; 87 | // invocation of the main function is necessary to initiate a connection to a DB 88 | const finder = `const findOne = async (args, conn)=>{ 89 | let outputer; 90 | const result = await conn.find({}).toArray(); 91 | for(let i=0; i< result.length; i++){ 92 | if(result[i]._id == args._id){ 93 | outputer = result[i]; 94 | } 95 | } 96 | return outputer; 97 | }`; 98 | /* finder is here as a var to utilize logic of FindOne in mango DB, 99 | but unfortunately mongoose for some reason don't want to retrieve only one element, 100 | so we as a team had to come up with a simple way to retrieve that data 101 | */ 102 | const createFindAllTables = (tableName) => { 103 | let str = ''; 104 | str += ` ${tableName.toLowerCase()} : 105 | {type:new GraphQLList(${tableName}), 106 | resolve: async (parent, args)=> 107 | {const result = await conn.collection("${tableName}").find({}).toArray(); return result;}}, `; 108 | str += ` ${tableName.toLowerCase()}FindById : 109 | {type:${tableName}, args: {_id:{type : GraphQLString}}, 110 | resolve: async (parent, args) => { const outputer = await findOne(args, conn.collection("${tableName}")); 111 | return outputer;}}`; 112 | return str; 113 | }; 114 | 115 | /* 116 | createFindAllTables function is actually merging two different GraphQl resolvers to find all the 117 | fields in tables and find by id on specific table entry. 118 | Nobody probably would read this but in case you are - have some fun, 119 | this project was a pure pleasure to work on, sharing wisdom - don't stress it over just slowly 120 | accustom urself to work with your team on solving problems that is the whole point of this!! 121 | Qleksii forever! 122 | */ 123 | 124 | const createSimpletype = (tableName, tableFields) => { 125 | let str = ''; 126 | // if(tableFields===null) return 'no fields were identified' 127 | str += ` 128 | const ${tableName} = new GraphQLObjectType({ name:'${tableName}', 129 | fields:() =>({`; 130 | for (let el in tableFields) { 131 | if (el === '__v') { 132 | continue; 133 | } 134 | str += el + ':{type:' + type(tableFields[el]) + '}, '; //needs to be typing 135 | } 136 | str += '})});'; 137 | return str; 138 | }; 139 | /* 140 | createSimpletype is a function that generates types of each collection so we know what fields 141 | exist in that collection and what types are they. 142 | */ 143 | 144 | const end = ` 145 | const newRunner = new GraphQLSchema({query:RootQuery}); 146 | app.use('/graphql', graphqlHTTP({ 147 | schema: newRunner, 148 | graphiql: true, 149 | }));`; 150 | /* 151 | end is actually an ending part of server file that is being generated 152 | */ 153 | 154 | const listen = ` 155 | app.listen(PORT, () => {console.log('listening on port 3200'); 156 | })`; 157 | /* 158 | at this point we need a listener for server file! 159 | */ 160 | const buildServerFile = async (req, res, next) => { 161 | const client = new MongoClient(req.body.URI); 162 | try { 163 | await client.connect(); 164 | 165 | const dbName = linkparser(req.body.URI); 166 | 167 | const tables = await listDatabases(client, dbName); 168 | 169 | const fields = await retrieveData(client, tables, dbName); 170 | 171 | let Resolvers = ` 172 | const RootQuery = new GraphQLObjectType({name:'Query', fields:{ `; 173 | const tail = `} });`; 174 | let result = ''; 175 | result += top; 176 | 177 | result += main(dbName, req.body.URI); 178 | 179 | result += mainR; 180 | 181 | result += finder; 182 | 183 | tables.forEach((l, i) => (result += createSimpletype(l, fields[i]))); 184 | 185 | result += Resolvers; 186 | 187 | tables.forEach((l) => { 188 | if ( 189 | result.slice(result.length - 9, result.length) === 190 | 'fields:{ ' 191 | ) 192 | result += createFindAllTables(l); 193 | else { 194 | result += ','; 195 | result += createFindAllTables(l); 196 | } 197 | }); 198 | result += tail; 199 | 200 | result += end; 201 | 202 | result += listen; 203 | await fs.writeFile( 204 | './remoteserver/server.js', 205 | result, 206 | function (err) { 207 | if (err) return console.log(err); 208 | } 209 | ); 210 | return next(); 211 | } catch (e) { 212 | //console.error(e); 213 | return next(e); 214 | } finally { 215 | await client.close(); 216 | } 217 | }; 218 | /* 219 | gBuild is where all logic for outputting the whole server file together. 220 | 221 | */ 222 | 223 | module.exports = buildServerFile; 224 | -------------------------------------------------------------------------------- /outputer.txt: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const PORT = 3200; 4 | const cors = require('cors'); 5 | const bodyParser = require('body-parser'); 6 | const { graphqlHTTP } = require('express-graphql'); 7 | const graphql = require('graphql'); 8 | const MongoClient = require('mongodb').MongoClient; 9 | const app = express(); 10 | let textsT; 11 | let conn; 12 | const mongoose = require('mongoose'); 13 | const {GraphQLObjectType, GraphQLList, GraphQLString, GraphQLSchema} = graphql;async function main(){ 14 | try { 15 | await mongoose.connect(mongodb+srv://alex:alex@cluster0.q9ag7.mongodb.net/myFirstDatabase?retryWrites=true&w=majority), 16 | {useCreateIndex: true, useNewUrlParser: true, useUnifiedTopology: true, dbName: myFirstDatabase},}).then() 17 | console.log("DB_connection is stable"); 18 | conn = await mongoose.connection.db; 19 | 20 | } catch (e) { 21 | console.error(e); 22 | } 23 | } main(); const findOne = async (args, conn)=>{ 24 | let outputer; 25 | const result = await conn.find({}).toArray(); 26 | for(let i=0; i< result.length; i++){ 27 | if(result[i]._id == args.id){ 28 | outputer = result[i]; 29 | } 30 | } 31 | return outputer; 32 | } const texts = new GraphQLObjectType({ name:'texts', 33 | fields:() =>({_id:{type:GraphQLString}, text:{type:GraphQLString}, NPI_ID:{type:GraphQLString}, CIN_ID:{type:GraphQLString}, createdAt:{type:GraphQLString}, })}); const users = new GraphQLObjectType({ name:'users', 34 | fields:() =>({_id:{type:GraphQLString}, mainPicture:{type:GraphQLString}, title:{type:GraphQLString}, firstName:{type:GraphQLString}, lastName:{type:GraphQLString}, DOB:{type:GraphQLString}, state:{type:GraphQLString}, city:{type:GraphQLString}, address:{type:GraphQLString}, CIN_ID:{type:GraphQLString}, email:{type:GraphQLString}, password:{type:GraphQLString}, })}); const doctors = new GraphQLObjectType({ name:'doctors', 35 | fields:() =>({_id:{type:GraphQLString}, mainPicture:{type:GraphQLString}, uffix:{type:GraphQLString}, firstName:{type:GraphQLString}, lastName:{type:GraphQLString}, speciality:{type:GraphQLString}, NPI_ID:{type:GraphQLString}, email:{type:GraphQLString}, password:{type:GraphQLString}, City:{type:GraphQLString}, })}); const sessions = new GraphQLObjectType({ name:'sessions', 36 | fields:() =>({_id:{type:GraphQLString}, UserName:{type:GraphQLString}, WhenLoggedIn:{type:GraphQLString}, Expiration:{type:GraphQLString}, })}); const RootQuery = new GraphQLObjectType({name:'Query', fields:{ textsFindById : 37 | {type:new GraphQLList(texts),args: {_id:{type : GraphQLString}}, 38 | resolve: async (parent, args) => { const outputer = findOne(args, conn.collection(texts)); 39 | return outputer;}} usersFindById : 40 | {type:new GraphQLList(users),args: {_id:{type : GraphQLString}}, 41 | resolve: async (parent, args) => { const outputer = findOne(args, conn.collection(users)); 42 | return outputer;}} doctorsFindById : 43 | {type:new GraphQLList(doctors),args: {_id:{type : GraphQLString}}, 44 | resolve: async (parent, args) => { const outputer = findOne(args, conn.collection(doctors)); 45 | return outputer;}} sessionsFindById : 46 | {type:new GraphQLList(sessions),args: {_id:{type : GraphQLString}}, 47 | resolve: async (parent, args) => { const outputer = findOne(args, conn.collection(sessions)); 48 | return outputer;}}} }); const newRunner = new GraphQLSchema({query:RootQuery}); 49 | app.get('/graphql', graphqlHTTP({ 50 | schema: newRunner, 51 | graphiql: true, 52 | }));app.listen(PORT, () => { 53 | }) -------------------------------------------------------------------------------- /p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLeksii/2d6e4b136e36bb09cb19341579e3c28cb7fc48d3/p.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qleksii", 3 | "productName": "Qleksii", 4 | "version": "1.0.0", 5 | "description": "Electron GraphQL tool for delivering you all your GraphQL needs", 6 | "main": "main.tsx", 7 | "scripts": { 8 | "test": "jest --verbose", 9 | "start": "electron-forge start", 10 | "pre": "node ./server/server.js", 11 | "watch": "webpack --config webpack.config.ts --watch", 12 | "package": "electron-forge package", 13 | "make": "electron-forge make", 14 | "build-installer": "electron-builder", 15 | "electron": "electron ." 16 | }, 17 | "build": { 18 | "appId": "qleksii-app", 19 | "win": { 20 | "target": [ 21 | "nsis" 22 | ], 23 | "icon": "icon.ico" 24 | }, 25 | "nsis": { 26 | "oneClick": false, 27 | "license": "license.txt", 28 | "allowToChangeInstallationDirectory": true 29 | } 30 | }, 31 | "keywords": [], 32 | "author": "Damien, Storm, Hasan, Oleksii, Dennis", 33 | "license": "ISC", 34 | "jest": { 35 | "globalSetup": "./jest-setup.js", 36 | "globalTeardown": "./jest-teardown.js" 37 | }, 38 | "dependencies": { 39 | "@babel/core": "^7.15.0", 40 | "@shelf/jest-mongodb": "^2.0.3", 41 | "@types/jest": "^27.0.1", 42 | "body-parser": "^1.19.0", 43 | "codemirror": "^5.62.3", 44 | "cors": "^2.8.5", 45 | "css-loader": "^6.2.0", 46 | "dotenv": "^10.0.0", 47 | "electron-squirrel-startup": "^1.0.0", 48 | "enzyme": "^3.11.0", 49 | "enzyme-adapter-react-16": "^1.15.6", 50 | "enzyme-to-json": "^3.6.2", 51 | "express": "^4.17.1", 52 | "express-graphql": "^0.12.0", 53 | "fs": "0.0.1-security", 54 | "graphql": "^15.5.1", 55 | "mongodb": "^4.1.0", 56 | "mongoose": "^6.0.5", 57 | "path": "^0.12.7", 58 | "react": "^16.9.2", 59 | "react-codemirror": "^1.0.0", 60 | "react-codemirror2": "^7.2.1", 61 | "react-dom": "^17.0.2", 62 | "sass": "^1.37.5", 63 | "sass-loader": "^12.1.0", 64 | "save-dev": "0.0.1-security", 65 | "style-loader": "^3.2.1", 66 | "ts-jest": "^27.0.5", 67 | "webpack": "^5.50.0", 68 | "webpack-cli": "^4.7.2" 69 | }, 70 | "devDependencies": { 71 | "@babel/preset-env": "^7.15.0", 72 | "@babel/preset-react": "^7.14.5", 73 | "@babel/preset-typescript": "^7.15.0", 74 | "@electron-forge/cli": "^6.0.0-beta.59", 75 | "@electron-forge/maker-deb": "^6.0.0-beta.59", 76 | "@electron-forge/maker-rpm": "^6.0.0-beta.59", 77 | "@electron-forge/maker-squirrel": "^6.0.0-beta.59", 78 | "@electron-forge/maker-zip": "^6.0.0-beta.59", 79 | "@types/react": "^17.0.18", 80 | "@types/react-dom": "^17.0.9", 81 | "@types/webpack-dev-server": "^3.11.5", 82 | "babel-core": "^6.26.3", 83 | "babel-jest": "^27.1.0", 84 | "babel-loader": "^8.2.2", 85 | "babel-polyfill": "^6.26.0", 86 | "babel-preset-es2015": "^6.24.1", 87 | "babel-preset-stage-0": "^6.24.1", 88 | "electron": "^13.2.2", 89 | "electron-builder": "^22.11.7", 90 | "electron-reload": "^2.0.0-alpha.1", 91 | "jest": "^27.1.0", 92 | "react-test-renderer": "^17.0.2", 93 | "supertest": "^6.1.6", 94 | "ts-node": "^10.2.0", 95 | "typescript": "^4.3.5", 96 | "webpack-dev-server": "^3.11.2" 97 | }, 98 | "config": { 99 | "forge": { 100 | "packagerConfig": {}, 101 | "makers": [ 102 | { 103 | "name": "@electron-forge/maker-squirrel", 104 | "config": { 105 | "name": "qleksii" 106 | } 107 | }, 108 | { 109 | "name": "@electron-forge/maker-zip", 110 | "platforms": [ 111 | "darwin" 112 | ] 113 | }, 114 | { 115 | "name": "@electron-forge/maker-deb", 116 | "config": {} 117 | }, 118 | { 119 | "name": "@electron-forge/maker-rpm", 120 | "config": {} 121 | } 122 | ] 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /remoteserver/package.txt: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remoteserver", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cors": "^2.8.5", 14 | "express": "^4.17.1", 15 | "express-graphql": "^0.12.0", 16 | "graphql": "^15.5.2", 17 | "mongoose": "^6.0.4", 18 | "path": "^0.12.7" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/GQLfactory/schema.js: -------------------------------------------------------------------------------- 1 | const type = require('./types'); 2 | // generates Schema to be used for queries, mutations, etc 3 | const schemaFactory = {}; 4 | schemaFactory.createSimpletype = (tableName, tableFields) => { 5 | let str = ''; 6 | 7 | str += `const ${tableName} = new GraphQLObjectType({ 8 | name:'${tableName}', 9 | fields:() =>({ 10 | `; 11 | 12 | for (let el in tableFields) { 13 | if (el === '__v') { 14 | continue; 15 | } 16 | str += 17 | el + 18 | `:{type: ${type(tableFields[el])} }, 19 | `; 20 | } 21 | str += `}) 22 | });`; 23 | return str; 24 | }; 25 | // creates a Schema for generating queries 26 | schemaFactory.createSimpleQuery = (tableName) => { 27 | let str = ``; 28 | str += `${tableName}:[${tableName}!]!, 29 | `; 30 | str += `${tableName.toLowerCase()}FindById(_id: ID!): ${tableName}, 31 | `; 32 | return str; 33 | }; 34 | 35 | // creates a Schema for generating mutations 36 | schemaFactory.createSimpleMutation = (tableName, tableFields) => { 37 | let str = ``; 38 | let schema = ``; 39 | for (let field in tableFields) { 40 | if (field === '__v' || field === '_id') { 41 | continue; 42 | } 43 | schema += `${field} : ${tableFields[field]}, 44 | `; 45 | } 46 | str += `update${tableName.toLowerCase()}ByID(_id: ID!, update:{${schema}}) : ${tableName}!, 47 | `; 48 | str += `delete${tableName.toLowerCase()}ByID(_id: ID!) : ${tableName}!, 49 | `; 50 | str += `add${tableName.toLowerCase()}ByID(_id: ID!, insert{${schema}}) : ${tableName}!, 51 | `; 52 | return str; 53 | }; 54 | 55 | // create Schema that will find all existing tables in the database 56 | schemaFactory.createFindAllTables = (tableName) => { 57 | let str = ` ${tableName.toLowerCase()} : { 58 | type:new GraphQLList(${tableName}), 59 | resolve(parent, args){ 60 | return ${tableName}.find({}); 61 | } 62 | }, 63 | `; 64 | return str; 65 | }; 66 | 67 | // create Schema that will search specific tables by field 68 | schemaFactory.createSearchByField = (tableName, checkbox) => { 69 | let str = ``; 70 | const fields = Object.keys(checkbox); 71 | fields.forEach((field) => { 72 | str += checkbox[field] 73 | ? `${tableName.toLowerCase()}By${field} : {type : ${tableName}, args : {${field}:{type : ${type( 74 | checkbox[field] 75 | )}}}, resolve(parent, args){return ${tableName}.findOne({${field}:args.${field}})}},` 76 | : ``; 77 | }); 78 | return str; 79 | }; 80 | // create Schema that will enable user to search using object ID 81 | schemaFactory.createSearchById = (tableName) => { 82 | let str = ` ${tableName.toLowerCase()}FindById : { 83 | type: ${tableName}, 84 | args: {_id:{type : GraphQLString}}, 85 | resolve(parent, args){ 86 | return ${tableName}.findOne({_id:args._id}); 87 | } 88 | }, 89 | `; 90 | return str; 91 | }; 92 | // create Schema that will delete tables 93 | schemaFactory.createDeleteByTable = (tableName) => { 94 | let str = `delete${tableName.toLowerCase()}ByID : { 95 | type : ${tableName}, 96 | args : {_id:{type : GraphQLString}}, 97 | resolve(parent, args){ 98 | return ${tableName}.deleteOne({_id:args._id}); 99 | } 100 | }, 101 | `; 102 | return str; 103 | }; 104 | // create schema that will update tables 105 | schemaFactory.createUpdateByTable = (tableName) => { 106 | let str = `update${tableName.toLowerCase()}ByID :{ 107 | type : ${tableName}, 108 | args : {_id:{type : GraphQLString}, update:{type : GraphQLObjectType}}, 109 | resolve(parent, args){ 110 | return ${tableName}.updateOne({_id:args._id}, args.update); 111 | } 112 | }, 113 | `; 114 | return str; 115 | }; 116 | // create schema that will add entries to tables 117 | schemaFactory.createAddByTable = (tableName) => { 118 | let str = `add${tableName.toLowerCase()}ByID :{ 119 | type : ${tableName}, 120 | args : {insert:{type : GraphQLObjectType}}, 121 | resolve(parent, args){ 122 | return ${tableName}.create(args.insert); 123 | } 124 | }, 125 | `; 126 | return str; 127 | }; 128 | 129 | module.exports = schemaFactory; 130 | -------------------------------------------------------------------------------- /server/GQLfactory/types.js: -------------------------------------------------------------------------------- 1 | // modifies the string input to specific GraphQL entries 2 | const type = (str) => { 3 | switch (str) { 4 | case 'array': 5 | return 'GraphQLList'; 6 | case 'object': 7 | return 'GraphQLString'; 8 | case 'string': 9 | return 'GraphQLString'; 10 | case 'number': 11 | return 'GraphQLString'; 12 | default: 13 | return 'GraphQLString'; 14 | } 15 | }; 16 | 17 | module.exports = type; 18 | -------------------------------------------------------------------------------- /server/controller/GQLcontroller.js: -------------------------------------------------------------------------------- 1 | const schemaFactory = require('../GQLfactory/schema'); 2 | const fs = require('fs'); 3 | const GQLController = {}; 4 | 5 | /* GQL Controller.createGQLSchema is a 6 | function that uses other methods and functions 7 | and generates the final results for Graphql queries 8 | and resolvers before it is being outputted in a side window in an app. 9 | here we have Resolvers, Mutations, Queries, Types! 10 | */ 11 | 12 | GQLController.createGQLSchema = (req, res, next) => { 13 | const fields = res.locals.db_data; 14 | const tables = res.locals.db_tables; 15 | try { 16 | const Types = []; 17 | let Resolvers = `const RootQuery = new GraphQLObjectType({ 18 | name:'Query', 19 | fields:{ 20 | `; 21 | let Mutations = `const Mutation = new GraphQLObjectType({ 22 | name:'Mutation', 23 | fields:{ 24 | `; 25 | let Query = `type Query { 26 | `; 27 | let Mutation = `type Mutation { 28 | `; 29 | for (let i = 0; i < tables.length; i++) { 30 | const types = schemaFactory.createSimpletype(tables[i], fields[i]); 31 | Types.push(types); 32 | const resolvers = 33 | schemaFactory.createFindAllTables(tables[i]) + 34 | schemaFactory.createSearchById(tables[i]); 35 | Resolvers += resolvers; 36 | const mutations = 37 | schemaFactory.createAddByTable(tables[i]) + 38 | schemaFactory.createUpdateByTable(tables[i]) + 39 | schemaFactory.createDeleteByTable(tables[i]); 40 | Mutations += mutations; 41 | Query += schemaFactory.createSimpleQuery(tables[i]); 42 | Mutation += schemaFactory.createSimpleMutation(tables[i], fields[i]); 43 | } 44 | const tail = `} 45 | } 46 | );`; 47 | Resolvers += tail; 48 | Mutations += tail; 49 | Query += `}`; 50 | Mutation += '}'; 51 | res.locals.GQLSchema = { Types, Resolvers, Mutations, Query, Mutation }; 52 | return next(); 53 | } catch (err) { 54 | const errObject = { 55 | log: `Error in createGQLSchema: ${err}`, 56 | status: 400, 57 | message: { 58 | err: 'Unable to create GQL schema', 59 | }, 60 | }; 61 | return next(errObject); 62 | } 63 | }; 64 | /* GQL Controller.pushToFile is actually a 65 | function that in the early stages of development would give 66 | people an ability to export all generated data to file 67 | */ 68 | GQLController.pushToFile = (req, res, next) => { 69 | try { 70 | fs.writeFile( 71 | 'outputer.txt', 72 | JSON.stringify(res.locals.GQLSchema), 73 | function (err) { 74 | if (err) return console.log(err); 75 | } 76 | ); 77 | return next(); 78 | } catch (err) { 79 | const errObject = { 80 | log: `Error in createGQLSchema: ${err}`, 81 | status: 400, 82 | message: { 83 | err: 'Unable to push to file', 84 | }, 85 | }; 86 | return next(errObject); 87 | } 88 | }; 89 | 90 | module.exports = GQLController; 91 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const PORT = 3333; 4 | const cors = require('cors'); 5 | const dbRetriver = require('../models/dbRetriver'); 6 | const GQLController = require('./controller/GQLcontroller'); 7 | const injection = require('../models/injection.js'); 8 | 9 | const app = express(); 10 | app.use(cors()); 11 | 12 | app.use(express.json()); 13 | 14 | // route for the post request to retrieve database 15 | app.post('/', dbRetriver.main, (req, res) => { 16 | return res 17 | .status(200) 18 | .json({ fields: res.locals.db_data, tables: res.locals.db_tables }); 19 | }); 20 | 21 | // route for post request to the injection middleware 22 | app.post('/injection', injection, (req, res) => { 23 | console.log('sending response'); 24 | return res.status(200).send('ok'); 25 | }); 26 | 27 | // route for post request to qltest to generate schemas 28 | app.post( 29 | '/qltest', 30 | dbRetriver.main, 31 | GQLController.createGQLSchema, 32 | (req, res) => { 33 | return res 34 | .status(200) 35 | .json({ 36 | data: res.locals.GQLSchema, 37 | fields: res.locals.db_data, 38 | tables: res.locals.db_tables, 39 | tests: 'test' 40 | }); 41 | } 42 | ); 43 | 44 | // global error handler 45 | app.use(function (err, req, res, next) { 46 | const defaultErr = { 47 | log: 'Express error handler caught unknown middleware error', 48 | status: 400, 49 | message: { err: 'An error occurred' }, 50 | }; 51 | const errorObj = Object.assign(defaultErr, err); 52 | console.log(errorObj.log); 53 | return res.status(errorObj.status).json(errorObj.message); 54 | }); 55 | 56 | module.exports = app.listen(PORT, () => { 57 | console.log(`Listening on PORT ${PORT}`); 58 | }); 59 | -------------------------------------------------------------------------------- /splash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | hfhfh 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { render } from 'react-dom'; 4 | import { MainContainer } from './containers/MainContainer'; 5 | import './style/app.scss'; 6 | 7 | class App extends Component { 8 | render() { 9 | return ( 10 | <> 11 | {/*renders the component main container into the */} 12 | 13 | 14 | ); 15 | } 16 | } 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /src/components/Card.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react'; 2 | 3 | type CardsProps = { 4 | table: string; 5 | fields: any; 6 | }; 7 | // create a card component for each collections and tables 8 | const Card: FunctionComponent = ({ table, fields }) => { 9 | const arr = []; 10 | // push the collections and tables extracted from the database 11 | for (let el in fields) { 12 | if (el === null) { 13 | arr.push('no documents registered yet'); 14 | } 15 | arr.push( 16 | el !== '__v' ? ( 17 |
18 |
  • {el + ' : '}
  • {' '} 19 |
  • 20 | {fields[el][0].toUpperCase() + fields[el].slice(1)} 21 |
  • 22 |
    23 | ) : null 24 | ); 25 | } 26 | // render the collections and tables from the array generated by the card function 27 | return ( 28 |
    29 |
    30 |

    {table}

    31 |
    32 |
    {arr}
    33 |
    34 |
    35 | ); 36 | }; 37 | 38 | export default Card; 39 | -------------------------------------------------------------------------------- /src/components/CodeMirror.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react'; 2 | import { UnControlled } from 'react-codemirror2'; 3 | 4 | type props = { 5 | value: string; 6 | }; 7 | // renders the queries, etc. in a code editor on the electron app 8 | export const CodeMirror: FunctionComponent = ({ value }) => { 9 | return ( 10 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/Darkmode.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react'; 2 | // component for the dark mode functionality 3 | export const Darkmode: FunctionComponent = () => { 4 | return ( 5 |
    6 |
    Dark Mode
    7 | 17 |
    18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/GraphQLSidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useState } from 'react'; 2 | import { CodeMirror } from '../components/CodeMirror'; 3 | import SandBox from './SandBox'; 4 | 5 | type props = { 6 | data: igraphQLData; 7 | uri: string; 8 | }; 9 | 10 | // stores the string generated from the function component 11 | interface igraphQLData { 12 | Resolvers: string; 13 | Types: string[]; 14 | Mutations: string; 15 | Query: string; 16 | Mutation: string; 17 | } 18 | 19 | // exports the table, fields, etc 20 | export const GraphQLSidebar: FunctionComponent = ({ data, uri }) => { 21 | const [display, setDisplay] = useState(''); 22 | const [code, setCode] = useState(''); 23 | const [codeIsOpen, setCodeIsOpen] = useState(false); 24 | 25 | // constructs the string to be provided to the interface 26 | const { Resolvers, Types, Mutations, Query, Mutation } = data; 27 | let type = ''; 28 | for (let i = 0; i < Types.length; i++) { 29 | type += `${Types[i]} 30 | 31 | `; 32 | } 33 | function handleClick(event: any) { 34 | setDisplay(event.target.value); 35 | } 36 | // renders the populated entries from interface once button is clicked 37 | return ( 38 |
    39 | 42 | 45 | 48 | 51 | 54 | {/*Once clicked, renders a GraphiQL interface that includes all the queries, resolvers, etc*/} 55 | 56 | 57 | {display === 'Resolvers' ? ( 58 |
      59 |
    • Resolvers
    • 60 |
        61 | {' '} 62 |
      {' '} 63 |
    64 | ) : null} 65 | {display === 'Types' ? ( 66 |
      67 |
    • Types
    • 68 |
        69 | 70 |
      {' '} 71 |
    72 | ) : null} 73 | {display === 'Mutations' ? ( 74 |
      75 |
    • Mutations
    • 76 |
        77 | 78 |
      {' '} 79 |
    80 | ) : null} 81 | {display === 'Query' ? ( 82 |
      83 |
    • Query
    • 84 |
        85 | 86 |
      {' '} 87 |
    88 | ) : null} 89 | {display === 'Query Mutation' ? ( 90 |
      91 |
    • Query Mutation
    • 92 |
        93 | 94 |
      {' '} 95 |
    96 | ) : null} 97 | 98 | {/* {display !== '' ? :null} */} 99 | 100 | {/*
      101 |
    • Resolvers
    • 102 |
        103 | {Resolvers} 104 |
      105 |
    106 |
      107 |
    • Types
    • 108 |
        109 | {typeArr} 110 |
      111 |
    112 |
      113 |
    • Mutations
    • 114 |
        115 | {Mutations} 116 |
      117 |
    */} 118 |
    119 | ); 120 | }; 121 | -------------------------------------------------------------------------------- /src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react'; 2 | import { Darkmode } from './Darkmode'; 3 | type props = { 4 | onMenuToggle: () => void; 5 | }; 6 | // create a navbar that has Dark Mode and menu button components 7 | export const Navbar: FunctionComponent = ({ onMenuToggle }) => { 8 | return ( 9 |
    10 |
      11 | {/*
    • 12 | 13 |
    • */} 14 |
    • 15 | 16 |
    • 17 |
    • 18 | 23 |
    • 24 |
    25 |
    26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/SandBox.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react'; 2 | 3 | interface Props { 4 | uri: string; 5 | } 6 | // create a function SandBox that sends a post request from the URL input to the path injection 7 | const SandBox: FunctionComponent = ({ uri }) => { 8 | const startSandBox = () => { 9 | console.log('button click', uri); 10 | fetch('http://localhost:3333/injection', { 11 | method: 'POST', 12 | headers: { 13 | 'Content-Type': 'application/json', 14 | }, 15 | body: JSON.stringify({ URI: uri }), 16 | }) 17 | .then((res) => { 18 | console.log('response recieved', res); 19 | window.darkMode.switch(); 20 | }) 21 | .catch((err) => console.log('Error writing remote sever')); 22 | }; 23 | return ; 24 | }; 25 | 26 | export default SandBox; 27 | -------------------------------------------------------------------------------- /src/components/UriMenu.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useState } from 'react'; 2 | import { Navbar } from './Navbar'; 3 | type props = { 4 | handleClick: (data: igraphQLData, fields: [], tables: []) => void; 5 | handleURI: (uri: string) => void; 6 | }; 7 | interface igraphQLData { 8 | Resolvers: string; 9 | Types: string[]; 10 | Mutations: string; 11 | Query: string; 12 | Mutation: string; 13 | } 14 | // component for the URI input 15 | export const UriMenu: FunctionComponent = ({ 16 | handleClick, 17 | handleURI, 18 | }) => { 19 | const [change, setChange] = useState(''); 20 | // sends a post request with URI as JSON request body to the server 21 | const handleSubmit = () => { 22 | fetch('http://localhost:3333/qltest', { 23 | method: 'POST', 24 | headers: { 25 | 'Content-Type': 'application/json', 26 | }, 27 | body: JSON.stringify({ URI: change }), 28 | }) 29 | .then((response) => response.json()) 30 | .then((body) => { 31 | handleURI(change); 32 | console.log(body); 33 | handleClick(body.data, body.fields, body.tables); 34 | }) 35 | .catch((error) => console.log('Error', error)); 36 | }; 37 | return ( 38 | // renders the URI component to the app 39 |
    40 |
    41 | { 45 | setChange(e.target.value); 46 | }} 47 | value={change} 48 | placeholder='Write Uri Here' 49 | /> 50 | 53 |
    54 |
    55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/Visualizer.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react'; 2 | import Card from './Card'; 3 | import { useEffect } from 'react'; 4 | 5 | interface Props { 6 | tables: Array; 7 | fields: Array; 8 | uri: string; 9 | } 10 | // generates the table and fields for the visualizer component 11 | const Visualizer: FunctionComponent = ({ tables, fields, uri }) => { 12 | const output = tables.map((el, i) => ( 13 | 14 | )); 15 | 16 | return ( 17 | // renders the output array from the generated tables and fields 18 |
    19 |
    {output}
    20 |
    21 | ); 22 | }; 23 | 24 | export default Visualizer; 25 | -------------------------------------------------------------------------------- /src/containers/MainContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useState } from 'react'; 2 | import { VisualContainer } from './VisualContainer'; 3 | import { UriMenu } from '../components/UriMenu'; 4 | import { Navbar } from '../components/Navbar'; 5 | import Visualizer from '../components/Visualizer'; 6 | 7 | interface igraphQLData { 8 | Resolvers: string; 9 | Types: string[]; 10 | Mutations: string; 11 | Query: string; 12 | Mutation: string; 13 | } 14 | 15 | // create a main container component 16 | export const MainContainer: FunctionComponent = () => { 17 | const [hasURI, setHasURI] = useState(false); 18 | const [data, setData] = useState({ 19 | Resolvers: '', 20 | Types: [], 21 | Mutations: '', 22 | Query: '', 23 | Mutation: '', 24 | }); 25 | const [uri, setURI] = useState(''); 26 | const [fields, setFields] = useState([]); 27 | const [tables, setTables] = useState([]); 28 | const [isMenuOpen, setMenuToOpen] = useState(false); 29 | const handleClick = (data: igraphQLData, fields: [], tables: []) => { 30 | setData(data); 31 | setFields(fields); 32 | setTables(tables); 33 | setHasURI(!hasURI); 34 | }; 35 | 36 | function handleURI(uri: string) { 37 | setURI(uri); 38 | } 39 | console.log(tables); 40 | return ( 41 | // renders the div class that includes uri, fields, and tables 42 |
    43 | setMenuToOpen(!isMenuOpen) : () => null} 45 | /> 46 | {hasURI ? ( 47 | 54 | ) : ( 55 | 56 | )} 57 |
    58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /src/containers/VisualContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useState, useEffect } from 'react'; 2 | import Visualizer from '../components/Visualizer'; 3 | import { Navbar } from '../components/Navbar'; 4 | import { GraphQLSidebar } from '../components/GraphQLSidebar'; 5 | ('../components/GraphQLSidebar'); 6 | 7 | type props = { 8 | tables: Array; 9 | fields: Array; 10 | uri: string; 11 | data: igraphQLData; 12 | isMenuOpen: Boolean; 13 | }; 14 | // stores generated string from post request to server 15 | interface igraphQLData { 16 | Resolvers: string; 17 | Types: string[]; 18 | Mutations: string; 19 | Query: string; 20 | Mutation: string; 21 | } 22 | // create a VisualContainer component that contains fields, tables, and uri 23 | export const VisualContainer: FunctionComponent = ({ 24 | fields, 25 | tables, 26 | uri, 27 | data, 28 | isMenuOpen, 29 | }) => { 30 | 31 | return ( 32 | // renders the fields, tables, and uri on the visual container div 33 |
    34 |
    35 | 36 | {!isMenuOpen ? null : } 37 |
    38 |
    39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | declare global { 5 | // renders the window with the darkmode functionality off on load 6 | interface Window { 7 | darkMode: { 8 | toggle: () => void; 9 | switch: () => void; 10 | }; 11 | } 12 | } 13 | ReactDOM.render(, document.getElementById('root')); 14 | -------------------------------------------------------------------------------- /src/style/_variables.scss: -------------------------------------------------------------------------------- 1 | $primary-light: #f7f6f2; 2 | $primary-dark: #2b2b2b; 3 | $navbar: #133b5c; 4 | -------------------------------------------------------------------------------- /src/style/app.scss: -------------------------------------------------------------------------------- 1 | @import './variables'; 2 | @import './navbar'; 3 | @import './sidebar'; 4 | @import './darkmode'; 5 | @import 'codemirror/lib/codemirror.css'; 6 | 7 | html, 8 | body { 9 | height: 100%; 10 | margin: 0; 11 | } 12 | 13 | h1 { 14 | color: white; 15 | background-color: $primary-light; 16 | } 17 | .MainContainer { 18 | display: flex; 19 | flex-direction: column; 20 | align-items: stretch; 21 | justify-content: space-between; 22 | } 23 | .wrapper__Uri { 24 | display: flex; 25 | flex-direction: column; 26 | align-items: center; 27 | justify-content: space-between; 28 | text-align: center; 29 | } 30 | 31 | .URI_Bar { 32 | display: flex; 33 | flex-direction: column; 34 | align-items: center; 35 | justify-content: space-around; 36 | } 37 | 38 | .Input__uri { 39 | margin-top: 40%; 40 | width: 400px; 41 | height: 2rem; 42 | border-radius: 10px; 43 | margin-bottom: 10px; 44 | } 45 | 46 | input:focus { 47 | outline: none; 48 | } 49 | 50 | .sendURI { 51 | border-color: #2ecc40; 52 | color: #eeeeee; 53 | background-color: #2ecc40; 54 | height: 1.75rem; 55 | border-radius: 5px; 56 | } 57 | 58 | .sendURI:hover { 59 | background-color: darken(#2ecc40, 10%); 60 | color: ligthen(#eeeeee, 40%); 61 | border-color: darken(#2ecc40, 10%); 62 | } 63 | 64 | .container { 65 | display: flex; 66 | flex-direction: row; 67 | height: 100%; 68 | width: 100%; 69 | justify-content: space-around; 70 | overflow-x: none; 71 | } 72 | 73 | .VisualContainer { 74 | display: flex; 75 | flex-direction: column; 76 | align-items: center; 77 | justify-content: flex-start; 78 | } 79 | 80 | .wrapper__visualizer { 81 | display: flex; 82 | width: auto; 83 | height: auto; 84 | flex-wrap: wrap; 85 | margin-right: 0; 86 | margin-left: 0; 87 | padding: 1% 5%; 88 | justify-content: space-between; 89 | } 90 | 91 | .text { 92 | margin-top: 10px; 93 | margin-left: 1%; 94 | } 95 | 96 | // @media (prefers-color-scheme: dark) { 97 | // body { background: #333; color: white; } 98 | // .logo { 99 | // background-image: url("/icon_dark.png"); 100 | 101 | // } 102 | // } 103 | 104 | // @media (prefers-color-scheme: light) { 105 | // body { background: #ddd; color: black; } 106 | // } 107 | 108 | .wrapper { 109 | width: 16rem; 110 | border: 1px solid; 111 | padding: 10px; 112 | justify-content: space-between; 113 | border-radius: 15px; 114 | margin: 10px; 115 | } 116 | 117 | .table_header { 118 | text-align: center; 119 | } 120 | 121 | .table_fields { 122 | list-style: none; 123 | display: flex; 124 | flex-direction: column; 125 | } 126 | 127 | .list { 128 | list-style: none; 129 | display: flex; 130 | flex-direction: row; 131 | justify-content: space-between; 132 | } 133 | 134 | // .sidebar-list-title { 135 | // list-style: none; 136 | // text-align: center; 137 | // padding: 0; 138 | // justify-content: center; 139 | // .sidebar-list-info { 140 | // text-align: left; 141 | // list-style: none; 142 | // font-size: 1rem; 143 | // } 144 | // } 145 | 146 | // .sidebar-menu { 147 | // width: 25rem; 148 | // text-align: center; 149 | // height: 100%; 150 | // } 151 | 152 | .logo { 153 | background-image: url('/icon.png'); 154 | } 155 | 156 | .codeMirror { 157 | width: 100%; 158 | height: 60vh; 159 | } 160 | #darkSide { 161 | background-image: url('/p.png'); 162 | } 163 | -------------------------------------------------------------------------------- /src/style/darkmode.scss: -------------------------------------------------------------------------------- 1 | // this is for dark mode theme 2 | 3 | .switch { 4 | position: relative; 5 | display: inline-block; 6 | width: 90px; 7 | height: 34px; 8 | } 9 | 10 | .switch input { 11 | display: none; 12 | } 13 | 14 | .switch:hover { 15 | opacity: 0.9; 16 | } 17 | 18 | .slider { 19 | position: absolute; 20 | cursor: pointer; 21 | top: 0; 22 | left: 0; 23 | right: 0; 24 | bottom: 0; 25 | background-color: #ca2222; 26 | -webkit-transition: 0.4s; 27 | transition: 0.4s; 28 | border-radius: 34px; 29 | } 30 | 31 | .slider:before { 32 | position: absolute; 33 | content: ''; 34 | height: 26px; 35 | width: 26px; 36 | left: 4px; 37 | bottom: 4px; 38 | background-color: white; 39 | -webkit-transition: 0.4s; 40 | transition: 0.4s; 41 | border-radius: 50%; 42 | } 43 | 44 | input:checked + .slider { 45 | background-color: #2ab934; 46 | } 47 | 48 | input:focus + .slider { 49 | box-shadow: 0 0 1px #2196f3; 50 | } 51 | 52 | input:checked + .slider:before { 53 | -webkit-transform: translateX(26px); 54 | -ms-transform: translateX(26px); 55 | transform: translateX(55px); 56 | } 57 | 58 | /*------ ADDED CSS ---------*/ 59 | .slider:after { 60 | content: 'OFF'; 61 | color: white; 62 | display: block; 63 | position: absolute; 64 | transform: translate(-50%, -50%); 65 | top: 50%; 66 | left: 50%; 67 | font-size: 10px; 68 | font-family: Verdana, sans-serif; 69 | } 70 | 71 | input:checked + .slider:after { 72 | content: 'ON'; 73 | } 74 | .dark_mode { 75 | font-size: 12px; 76 | font-family: Monospace; 77 | } 78 | 79 | .text { 80 | margin-top: 10px; 81 | margin-left: 1%; 82 | } 83 | 84 | @media (prefers-color-scheme: dark) { 85 | body { 86 | background: $primary-dark; 87 | color: #eeeeee; 88 | } 89 | .codeMirror { 90 | background: $primary-dark; 91 | color: #eeeeee; 92 | } 93 | .CodeMirror-linenumbers { 94 | background: $primary-dark; 95 | color: #eeeeee; 96 | } 97 | .table_header { 98 | border: 2px darken($primary-dark, 80%); 99 | } 100 | .wrapper { 101 | box-shadow: 3px 8px darken($primary-dark, 10%); 102 | } 103 | } 104 | 105 | @media (prefers-color-scheme: light) { 106 | body { 107 | background: $primary-light; 108 | color: #171010; 109 | } 110 | .codeMirror { 111 | background: $primary-light; 112 | color: #171010; 113 | } 114 | .CodeMirror-linenumbers { 115 | background: $primary-light; 116 | color: #171010; 117 | } 118 | .table_header { 119 | border: 2px darken($primary-light, 80%); 120 | } 121 | .wrapper { 122 | box-shadow: 3px 8px darken($primary-light, 10%); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/style/navbar.scss: -------------------------------------------------------------------------------- 1 | // this is for navbar css 2 | .navbar { 3 | background-color: lighten($navbar, 30%); 4 | ul { 5 | display: flex; 6 | flex-direction: row; 7 | list-style: none; 8 | justify-content: space-between; 9 | margin-right: 2rem; 10 | } 11 | margin-bottom: 0; 12 | margin-top: 0; 13 | padding: 3px; 14 | width: 100%; 15 | } 16 | 17 | .logo { 18 | width: 4rem; 19 | } 20 | 21 | .menu-button { 22 | vertical-align: center; 23 | width: 4rem; 24 | } 25 | 26 | .menu-button:hover { 27 | opacity: 0.75; 28 | } 29 | -------------------------------------------------------------------------------- /src/style/sidebar.scss: -------------------------------------------------------------------------------- 1 | // this is for sidebar css 2 | .sidebar-list { 3 | list-style: none; 4 | } 5 | 6 | .sidebar-list-title { 7 | list-style: none; 8 | text-align: center; 9 | padding: 0; 10 | justify-content: center; 11 | .sidebar-list-info { 12 | text-align: left; 13 | list-style: none; 14 | font-size: 1rem; 15 | } 16 | } 17 | 18 | .sidebar-menu { 19 | width: 40rem; 20 | text-align: center; 21 | height: 100%; 22 | margin-left: 0; 23 | margin-right: 0; 24 | margin-bottom: 0; 25 | overflow-x: scroll; 26 | } 27 | -------------------------------------------------------------------------------- /src/style/visualizer.scss: -------------------------------------------------------------------------------- 1 | // this is for visualizer scss files 2 | .wrapper__visualizer { 3 | display: flex; 4 | flex-direction: row; 5 | overflow: wrap; 6 | width: auto; 7 | height: auto; 8 | } 9 | -------------------------------------------------------------------------------- /sum.js: -------------------------------------------------------------------------------- 1 | function sum(a, b) { 2 | return a + b; 3 | } 4 | module.exports = sum; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "allowSyntheticDefaultImports": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "noEmit": true, 14 | "jsx": "react" 15 | }, 16 | "include": ["src"] 17 | } -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import webpack from "webpack"; 3 | import CodeMirror from 'react-codemirror2'; 4 | 5 | const config: webpack.Configuration = { 6 | mode: 'development', 7 | entry: "./src/index.tsx", 8 | devtool: 'inline-source-map', 9 | target: 'electron-renderer', 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.(ts|js)x?$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader", 17 | options: { 18 | presets: [ 19 | "@babel/preset-env", 20 | "@babel/preset-react", 21 | "@babel/preset-typescript", 22 | ], 23 | }, 24 | }, 25 | }, 26 | { 27 | test: [/\.s[ac]ss$/i, /\.css$/i], 28 | use: [ 29 | // Creates `style` nodes from JS strings 30 | 'style-loader', 31 | // Translates CSS into CommonJS 32 | 'css-loader', 33 | // Compiles Sass to CSS 34 | 'sass-loader', 35 | ], 36 | } 37 | ], 38 | }, 39 | resolve: { 40 | extensions: [".tsx", ".ts", ".js"], 41 | }, 42 | output: { 43 | path: path.resolve(__dirname, "build"), 44 | filename: "bundle.js", 45 | }, 46 | devServer: { 47 | contentBase: path.join(__dirname, "build"), 48 | compress: true, 49 | port: 4000, 50 | }, 51 | }; 52 | 53 | export default config; -------------------------------------------------------------------------------- /workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "remoteAuthority": "wsl+Ubuntu-20.04" 8 | } --------------------------------------------------------------------------------