├── .babelrc
├── .env
├── .gitignore
├── LICENSE
├── README.md
├── assets
├── PeachLogo.png
├── PeachQE-3.png
├── PeachQE.png
├── PeachQE_Install_Guide.gif
├── icons
│ ├── mac
│ │ └── icon.icns
│ ├── png
│ │ └── icon.png
│ └── win
│ │ └── icon.ico
└── readme_imgs
│ ├── Peach-Mode-Example.jpeg
│ └── Relay-Proper-Example.jpeg
├── electron
├── menu.js
└── newWindow.js
├── html
├── addWindow.html
├── peachWindow.html
└── peachWindow2.html
├── main.js
├── package-lock.json
├── package.json
├── queryHistory.json
├── schema.graphql
├── src
├── App.js
├── __generated__
│ └── AppQuery.graphql.js
├── components
│ ├── Downloader
│ │ └── index.js
│ ├── ErrorBoundary.js
│ ├── Footer.js
│ ├── ImportedMode
│ │ ├── TestUpload.js
│ │ ├── Uploader.js
│ │ ├── index.js
│ │ ├── renderer.js
│ │ ├── styles
│ │ │ └── uploader.css
│ │ ├── upload-files.component.js
│ │ └── watcher.js
│ ├── Logo.js
│ ├── Navbar.js
│ ├── QueryContainer
│ │ ├── History.js
│ │ └── index.js
│ ├── QueryEditor
│ │ └── index.js
│ ├── QuerySelector
│ │ ├── QueryButton.js
│ │ └── index.js
│ ├── ResponseDisplay
│ │ ├── ImportedResponseDisplay.js
│ │ ├── Response.js
│ │ └── WrittenResponseDisplay.js
│ ├── SchemaDisplay
│ │ ├── SchemaDisplayContainer.js
│ │ ├── SchemaSearch.js
│ │ └── index.js
│ ├── SchemaDownload
│ │ ├── FileDownloader.js
│ │ ├── InputGqlSchema.js
│ │ ├── Modal.js
│ │ └── SchemaUrlInput.js
│ ├── StoreDisplay
│ │ └── StoreDisplay.js
│ ├── VariableInput
│ │ └── index.js
│ ├── hooks
│ │ ├── http-common.js
│ │ ├── upload-files.service.js
│ │ ├── uploaderHook.js
│ │ └── useFileDownloader.js
│ └── icon.png
├── database
│ ├── db.js
│ ├── importedHistory.json
│ ├── queryHistory.json
│ └── schemaHistory.json
├── index.js
├── relay
│ ├── RelayEnvironment.js
│ ├── __generated__
│ │ ├── importedLongMediaQuery.graphql.js
│ │ ├── importedMediaQuery.graphql.js
│ │ ├── importedThreadQuery.graphql.js
│ │ └── writtenQuery.graphql.js
│ ├── aliasID.js
│ ├── fetchGraphQL.js
│ ├── gqlEndpoint.js
│ ├── imported.js
│ ├── makeJsonSchema.js
│ └── written.js
└── styles
│ ├── App.css
│ ├── Modal.css
│ ├── downloader.css
│ └── styles.css
├── webpack.build.config.js
└── webpack.dev.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-react", "@babel/preset-env"],
3 | "plugins": [
4 | ["macros"],
5 | ["relay"],
6 | ["@babel/plugin-transform-runtime", {
7 | "regenerator": true
8 | }],
9 | ["wildcard"]
10 | ],
11 | "ignore": [
12 | "_generated_"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build folder and files #
2 | ##########################
3 | release-builds/
4 |
5 | # Development folders and files #
6 | #################################
7 | .tmp/
8 | dist/
9 | node_modules/
10 | package-lock.json
11 | *.compiled.*
12 |
13 | # Folder config file #
14 | ######################
15 | Desktop.ini
16 |
17 | # Folder notes #
18 | ################
19 | _ignore/
20 |
21 | # Log files & folders #
22 | #######################
23 | logs/
24 | *.log
25 | npm-debug.log*
26 | .npm
27 |
28 | # Packages #
29 | ############
30 | # it's better to unpack these files and commit the raw source
31 | # git has its own built in compression methods
32 | *.7z
33 | *.dmg
34 | *.gz
35 | *.iso
36 | *.jar
37 | *.rar
38 | *.tar
39 | *.zip
40 |
41 | # Photoshop & Illustrator files #
42 | #################################
43 | *.ai
44 | *.eps
45 | *.psd
46 |
47 | # Windows & Mac file caches #
48 | #############################
49 | .DS_Store
50 | Thumbs.db
51 | ehthumbs.db
52 |
53 | # Windows shortcuts #
54 | #####################
55 | *.lnk
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 OSLabs Beta
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
README
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Peach QE
20 |
21 | A Relay visualizer for all your GraphQL queries.
22 |
23 | Homepage →
24 | Product Hunt Page →
25 |
26 |
27 | Explore the docs »
28 |
29 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Table of Contents
51 |
52 |
53 | About The Project
54 |
57 |
58 | Deployment
59 |
60 | Getting Started
61 |
65 |
66 | Deployment
67 | License
68 |
69 | Acknowledgements
70 |
71 |
72 |
73 |
74 |
75 |
76 | ## About The Project
77 |
78 | Peach QE is the first Relay visualizer to handle all your GraphQL queries under one roof, directly on your desktop.
79 |
80 | Upload pre-written queries, live-edit the queries you want to explore, variable inputs, and even handle as many databases as required.
81 |
82 |
83 |
84 | ### Built With
85 |
86 |
87 | * [Relay](https://relay.dev/)
88 | * [GraphQL](https://graphql.org/)
89 | * [React](https://reactjs.org/)
90 | * [Electron](https://www.electronjs.org/)
91 | * [Webpack](https://webpack.js.org/)
92 |
93 |
94 |
95 |
96 |
97 |
98 | ## Getting Started
99 |
100 | * Fork and Clone the Repo:
101 |
102 | ```sh
103 | git clone https://github.com/oslabs-beta/peach.git
104 | ```
105 |
106 |
107 |
108 | ### Prerequisites
109 |
110 | Install the dpendencies:
111 |
112 | * [ npm i ]
113 |
114 | ```sh
115 | npm install
116 | ```
117 | * Run the Electron App locally
118 |
119 | ```sh
120 | npm start
121 | ```
122 |
123 |
124 |
125 |
126 | ## Deployment
127 |
128 | 1. In order to build the executable file (aka, the desktop client) you will need to run specific commands for your OS:
129 |
130 | * Mac:
131 |
132 | ```sh
133 | npm run package-mac
134 | ```
135 |
136 | * Windows:
137 |
138 | ```sh
139 | npm run package-win
140 | ```
141 |
142 | * Linux:
143 |
144 | ```sh
145 | npm run package-linux
146 | ```
147 |
148 |
149 | 2. Look for the directory **Release-builds** in the root folder where you cloned this Repo. (created automatically by the previous command)
150 |
151 | 3. Find the specific folder for your OS. (e.g., *peachQE-win*)
152 |
153 | 4. The file **peachQE.exe** (in Windows) will launch the app in your machine.
154 |
155 |
156 |
157 | In **every** case, you will need to add the file [*schema.graphql*](https://github.com/oslabs-beta/peach/blob/dev/schema.graphql) to the root directory and the file [*imported.js*](https://github.com/oslabs-beta/peach/blob/dev/src/relay/imported.js) to the directory src>relay. For example:
158 |
159 | .
160 | ├── (Root) # Installed Root folder
161 | ├── schema.graphql
162 | ├── src
163 | ├── relay # Create both folders
164 | ├── imported.js
165 | │
166 | └── ...
167 |
168 |
169 |
170 | ## User Manual
171 |
172 | Once you have the application running, you will notice that PeachQE operates in two different "modes":
173 |
174 | ### Relay Proper
175 |
176 | When the application first opens, the window will by default display "Relay Proper" mode, which contains much of the core functionality of the application:
177 |
178 |
179 |
180 | 1. The left section of the window is reserved for the **Schema Display**. Here, you can:
181 | * View your current schema and type into the searchbar to find specific fields.
182 | * Import a new Schema, updating the contents of the schema display to contain the newly imported schema.
183 | * Update the GraphQL API url endpoint. This determines what API PeachQE is interacting with.
184 |
185 | 2. Below the **Schema Display** you will find the **Variable Input**. Here you can:
186 | * Manually enter variables as JSON objects.
187 | * The **Variable Input** will interact with the **Query Input** to set the response in the **Reponse Display**.
188 |
189 | 3. The central section is reserved for the **Query Input**. Here you can:
190 | * Manually type in valid GraphQL queries.
191 | * Select previous queries from the **Query History** dropdown. Once clicked the selected query will populate the Query Input Field.
192 | * **Submit** your query.
193 |
194 | 3. The right section is the **Response Display** which, upon clicking the **Submit Query** button, will display the reponses to any submitted query.
195 |
196 | ### Peach Mode
197 |
198 |
199 |
200 | By clicking the yellow button titled **Peach Mode** at the top right corner of the window, or simply by scrolling down, you will find our designated UI for **Relay** specific functionality.
201 |
202 | While the **Response Display** functions entirely the same, the central and left sections have been altered to accomodate for Relay-specific functionality.
203 |
204 | 1. The left section still contains a **Variable Input** with two additional fields:
205 | 1. The **Query File Uploader** simply directs you to the "Peaches" dropdown from the menu bar, where you can "Start a New Peach" by uploading a file containing a list of GraphQL Queries.
206 | 2. The **Store Display** offers you a direct visual of the Relay Store, a Relay-specific functionality that stores all the data sent by your GraphQL Queries, and all the data sent back from the responding GraphQL API. It is effectively a log of your interactions with a given GraphQl API, and it is made available with PeachQE!
207 |
208 | 2. The central section contains 2 new components:
209 | 1. The **Query Editor** where you may copy/paste or manually type in multiple GraphQl Queries. Upon clicking **Save Edited Query**, the user inputted text will be saved and rendered as **Relay** queries, which will be displayed in the **New Query Selector**.
210 | 2. The **New Query Selector** will contain a list of queries matching the queries inputted in the **Query Editor**. You may then toggle between these queries, and the **Response** display will update with the currently selected query.
211 |
212 |
213 | Enjoy!
214 |
215 |
216 |
217 |
218 |
219 | _For more information, please visit our [website](https://www.peachqe.io/)_
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 | ## License
228 |
229 | Distributed under the [MIT](https://github.com/oslabs-beta/peach/blob/dev/LICENSE) License. See [`LICENSE`](https://github.com/oslabs-beta/peach/blob/dev/LICENSE) for more information.
230 |
231 |
232 |
233 |
234 | ## Acknowledgements to the PEach QE team:
235 |
236 | * [Alura Chung-Mehdi](https://github.com/aluracm)
237 | * [Roland Wynter](https://github.com/Rcwynter)
238 | * [Graham Albachten](https://github.com/albachteng)
239 | * [Nakami Hope-Felix](https://github.com/Nuckaahf)
240 | * [Carlos Botero-Vargas](https://github.com/Carlos-BoteroVargas)
241 |
242 |
243 |
244 | -------------
245 |
246 |
247 | THANK YOU!
248 |
249 |
--------------------------------------------------------------------------------
/assets/PeachLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/assets/PeachLogo.png
--------------------------------------------------------------------------------
/assets/PeachQE-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/assets/PeachQE-3.png
--------------------------------------------------------------------------------
/assets/PeachQE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/assets/PeachQE.png
--------------------------------------------------------------------------------
/assets/PeachQE_Install_Guide.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/assets/PeachQE_Install_Guide.gif
--------------------------------------------------------------------------------
/assets/icons/mac/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/assets/icons/mac/icon.icns
--------------------------------------------------------------------------------
/assets/icons/png/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/assets/icons/png/icon.png
--------------------------------------------------------------------------------
/assets/icons/win/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/assets/icons/win/icon.ico
--------------------------------------------------------------------------------
/assets/readme_imgs/Peach-Mode-Example.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/assets/readme_imgs/Peach-Mode-Example.jpeg
--------------------------------------------------------------------------------
/assets/readme_imgs/Relay-Proper-Example.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/assets/readme_imgs/Relay-Proper-Example.jpeg
--------------------------------------------------------------------------------
/electron/menu.js:
--------------------------------------------------------------------------------
1 | /*
2 | This file holds the application's window menu structure
3 | */
4 |
5 | const createAddWindow = require('./newWindow')
6 | const { app } = require('electron');
7 |
8 | // evaluates to true if the platform is Mac
9 | const isMac = process.platform === 'darwin' ? true : false;
10 |
11 | // Create Menu template
12 | const mainMenuTemplate = [
13 | ...(isMac ? [{ role: 'appMenu' }] : []),
14 | {
15 | role: 'fileMenu'
16 | },
17 | {
18 | role: 'editMenu'
19 | },
20 | {
21 | label: 'Peaches',
22 | submenu: [
23 | {
24 | label: 'Start a New Peach',
25 | click(){
26 | createAddWindow();
27 | }
28 | },
29 | {
30 | label: 'Continue working with a Peach'
31 | }
32 | ]
33 | },
34 | ];
35 |
36 | // If OSX, add empty object to the start of menu
37 | if (process.platform == 'darwin') {
38 | mainMenuTemplate.unshift({label: app.getName()});
39 | }
40 |
41 | // * Add developer tools option if in dev
42 | if(process.env.NODE_ENV !== 'production'){
43 | mainMenuTemplate.push({
44 | label: 'Developer Tools',
45 | submenu:[
46 | { role: 'reload' },
47 | { role: 'forcereload' },
48 | { type: 'separator' },
49 | {
50 | label: 'Toggle DevTools',
51 | accelerator:process.platform == 'darwin' ? 'Command+I' : 'Ctrl+I',
52 | click(item, focusedWindow){
53 | focusedWindow.toggleDevTools();
54 | }
55 | }
56 | ]
57 | });
58 | }
59 |
60 | module.exports = mainMenuTemplate
--------------------------------------------------------------------------------
/electron/newWindow.js:
--------------------------------------------------------------------------------
1 | /*
2 | handles logic for generating a window that allows you to create a new project
3 | currently only functions as a text box
4 | */
5 |
6 | const path = require('path');
7 | const url = require('url');
8 | const { BrowserWindow, dialog, app, IPCRenderer } = require('electron');
9 | const mainMenuTemplate = require('./menu');
10 |
11 | let fs = require('fs')
12 |
13 | // check for development environment
14 | let isDev = false
15 |
16 | if (
17 | process.env.NODE_ENV !== undefined &&
18 | process.env.NODE_ENV === 'development'
19 | ) {
20 | isDev = true
21 | }
22 |
23 | // Handle add item window
24 | const createAddWindow = () => {
25 | addWindow = new BrowserWindow({
26 | width: 1155,
27 | height: 600,
28 | minHeight:400,
29 | minWidth:400,
30 | title: 'Upload your own Peach',
31 | backgroundcolor: 'white',
32 | // To have rounded corners, although it doesn't work for Mac
33 | // transparent: true,
34 | // The compiler is not recognizing the icon image. Keep commented out.
35 | // icon: `../assets/icons/png/icon.png`,
36 | // To remove the entire frame from Popup
37 | // frame: false,
38 | webPreferences: {
39 | nodeIntegration: true,
40 | contextIsolation: false,
41 | enableRemoteModule:true,
42 | worldSafeExecuteJavaScript: true,
43 | }
44 | });
45 | // To remove menu from Popup
46 | addWindow.removeMenu();
47 |
48 | // TODO To serve static html files
49 | addWindow.loadURL(url.format({
50 | pathname: path.join(__dirname, '../html/peachWindow2.html'),
51 | protocol: 'file:',
52 | slashes:true
53 | }));
54 |
55 | // ! Handle garbage collection
56 | addWindow.on('close', function(){
57 | addWindow = null;
58 | });
59 | };
60 |
61 | module.exports = createAddWindow;
--------------------------------------------------------------------------------
/html/addWindow.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Start a New Peach
6 |
7 |
8 |
9 |
17 |
(For now, close this popup by on the X, top rigth corner)
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/html/peachWindow.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 | Peach QE - Desktop Visualizer for React-Relay Applications
15 |
16 |
17 |
18 |
24 |
25 |
26 |
36 |
37 |
38 |
39 | The file content will be the same as the editor.
40 |
41 |
42 |
43 |
44 |
149 |
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/html/peachWindow2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Peach QE - Desktop Visualizer for React-Relay Applications
9 |
10 |
11 |
12 |
13 |
Instructions
14 |
How to upload your queries
15 |
16 | Write and save your queries on a simple (non-enriched) text file.
17 |
18 | Recommendations:
19 | - Follow these
20 | naming conventions.
21 |
22 | ⚬ Field names should use camelCase
.
23 | ⚬ Type names should use PascalCase
.
24 | ⚬ Enum names should use PascalCase
.
25 | ⚬ Enum values should use ALL_CAPS
.
26 | ⚬ Consider globally unique names for your variables.
27 |
28 |
29 |
30 |
31 | Drag & Drop the file on the peach-colored area on the right.
32 | Click Save Button
33 | Locate the Directory "RELAY"
34 | Replace the existing imported.js
file
35 | Close this window
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Drag your file here.
43 |
44 |
45 |
46 |
Save
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | This file holds the main window process for Electron, rendering the desktop window of the application
3 | */
4 | const path = require('path');
5 | const url = require('url');
6 | const { app, BrowserWindow, Menu} = require('electron');
7 | const mainMenuTemplate = require('./electron/menu');
8 | const remote = require('electron')
9 | const {dialog} = remote
10 | // const createAddWindow = require('./electron/newWindow')
11 |
12 | //***//
13 |
14 |
15 | let mainWindow
16 |
17 | // check for development environment
18 | let isDev = false
19 |
20 | if (
21 | process.env.NODE_ENV !== undefined &&
22 | process.env.NODE_ENV === 'development'
23 | ) {
24 | isDev = true
25 | }
26 |
27 | function createMainWindow() {
28 | mainWindow = new BrowserWindow({
29 | width: isDev ? 1400 : 1100,
30 | height: 800,
31 | show: false,
32 | backgroundcolor: 'white',
33 | icon: `${__dirname}/assets/icons/png/icon.png`,
34 | webPreferences: {
35 | nodeIntegration: true,
36 | contextIsolation: false,
37 | enableRemoteModule:true,
38 | worldSafeExecuteJavaScript: true,
39 | },
40 | })
41 |
42 | let indexPath
43 |
44 | // check for development mode options
45 | if (isDev && process.argv.indexOf('--noDevServer') === -1) {
46 | indexPath = url.format({
47 | protocol: 'http:',
48 | host: 'localhost:8080',
49 | pathname: 'index.html',
50 | slashes: true,
51 | })
52 | } else {
53 | indexPath = url.format({
54 | protocol: 'file:',
55 | pathname: path.join(__dirname, 'dist', 'index.html'),
56 | slashes: true,
57 | })
58 | }
59 |
60 | mainWindow.loadURL(indexPath)
61 |
62 | // Don't show until we are ready and loaded
63 | mainWindow.once('ready-to-show', () => {
64 | mainWindow.show()
65 |
66 | // Open devtools if dev
67 | if (isDev) {
68 | const {
69 | default: installExtension,
70 | REACT_DEVELOPER_TOOLS,
71 | } = require('electron-devtools-installer')
72 | // add react dev tools
73 | installExtension(REACT_DEVELOPER_TOOLS).catch((err) =>
74 | console.log('Error loading React DevTools: ', err)
75 | )
76 | // mainWindow.webContents.openDevTools()
77 | }
78 | })
79 |
80 | // ! Quit app when closed
81 | mainWindow.on('closed', () => app.quit());
82 | // redundant closinf window (Mac)
83 | mainWindow.on('closed', () => (mainWindow = null))
84 |
85 | // Build menu from template
86 | const mainMenu = Menu.buildFromTemplate(mainMenuTemplate);
87 | // Insert menu
88 | Menu.setApplicationMenu(mainMenu);
89 |
90 | }
91 |
92 | // must wait to load pages until the ready state has fired
93 | app.on('ready', createMainWindow)
94 |
95 | // it is common for apps to remain open until explicitly quit in Mac environment
96 | app.on('window-all-closed', () => {
97 | if (process.platform !== 'darwin') {
98 | app.quit()
99 | }
100 | })
101 |
102 | app.on('activate', () => {
103 | if (mainWindow === null) {
104 | createMainWindow()
105 | }
106 | })
107 |
108 | // Stop error, note may become deprecated
109 | app.allowRendererProcessReuse = true
110 |
111 | // TODO electron application codes
112 |
113 | const { ipcMain } = require('electron')
114 | let fs = require('fs')
115 |
116 |
117 | ipcMain.on('ondragstart', (event, filePath) => {
118 |
119 | readFile(filePath);
120 |
121 | function readFile(filepath) {
122 | fs.readFile(filepath, 'utf-8', (err, data) => {
123 |
124 | if(err){
125 | alert("An error ocurred reading the file :" + err.message)
126 | return
127 | }
128 |
129 | // handle the file content
130 | event.sender.send('fileData', data)
131 | })
132 | }
133 | })
134 |
135 |
136 |
137 | ipcMain.on('clickedbutton', (event, data) => {
138 |
139 | // console.log('This is the data we want to save: ', data)
140 | // Resolves to a Promise
141 | dialog.showSaveDialog({
142 | title: 'Select the File Path to save',
143 | defaultPath: path.join(__dirname, '../relay/imported/imported.js'),
144 | // defaultPath: path.join(__dirname, '../assets/'),
145 | buttonLabel: 'Save',
146 | filters: [
147 | {
148 | name: 'PeachQE - GraphQL pre-written queries',
149 | extensions: ['js']
150 | }
151 | ],
152 | properties: []
153 | }).then(file => {
154 | // Stating whether dialog operation was cancelled or not.
155 | console.log(file.canceled);
156 | if (!file.canceled) {
157 | console.log(file.filePath.toString());
158 |
159 | // Creating and Writing to the sample.txt file
160 | fs.writeFile(
161 | file.filePath.toString(),
162 | data,
163 | (err) => {
164 | if(err) {
165 | alert("An error ocurred updating the file"+ err.message);
166 | console.log(err);
167 | return;
168 | }
169 | console.log('Saved!');
170 | // alert("The file has been succesfully saved");
171 | })
172 | }
173 | }).catch(err => {
174 | console.log(err);
175 | });
176 | });
177 |
178 | //require in exec to run terminal commands in js:
179 | const execSync = require('child_process').execSync;
180 |
181 | ipcMain.on('close-me', (evt, arg) => {
182 | var addwindow = remote.createAddWindow()
183 | execSync('npm run relay', { encoding: 'utf-8' });
184 | addwindow.close()
185 | })
186 |
187 |
188 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peach-qe",
3 | "version": "1.0.0",
4 | "description": "relay visualizer - desktop app",
5 | "author": "OS Labs - Team Peach - CBV",
6 | "license": "MIT",
7 | "engines": {
8 | "node": ">=9.0.0",
9 | "npm": ">=5.0.0",
10 | "yarn": ">=1.0.0"
11 | },
12 | "browserslist": [
13 | "last 4 versions"
14 | ],
15 | "main": "main.js",
16 | "scripts": {
17 | "prod": "cross-env NODE_ENV=production webpack --mode production --config webpack.build.config.js && electron --noDevServer .",
18 | "start": "npm run relay && cross-env NODE_ENV=development webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js --mode development",
19 | "build": "cross-env NODE_ENV=production webpack --config webpack.build.config.js --mode production",
20 | "relay": "relay-compiler --schema ./schema.graphql --src ./src/ --watchman false $@",
21 | "package": "npm run build",
22 | "package-mac": "electron-packager . --overwrite --platform=darwin --arch=x64 --icon=assets/icons/mac/icon.icns --prune=true --out=release-builds",
23 | "package-win": "electron-packager . --overwrite --asar=true --platform=win32 --arch=ia32 --icon=assets/icons/win/icon.ico --prune=true --out=release-builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName=\"Peach QE - Relay Visualizer\"",
24 | "package-linux": "electron-packager . --overwrite --platform=linux --arch=x64 --icon=assets/icons/png/icon.png --prune=true --out=release-builds",
25 | "postpackage": "electron-packager ./ --out=./release-builds"
26 | },
27 | "dependencies": {
28 | "2": "^3.0.0",
29 | "@babel/plugin-transform-runtime": "^7.14.3",
30 | "@fortawesome/fontawesome-svg-core": "^1.2.28",
31 | "@fortawesome/free-solid-svg-icons": "^5.13.0",
32 | "@fortawesome/react-fontawesome": "^0.1.9",
33 | "axios": "^0.21.1",
34 | "babel-plugin-macros": "^2.8.0",
35 | "babel-plugin-wildcard": "^7.0.0",
36 | "bootstrap": "^5.0.1",
37 | "codemirror": "^5.61.1",
38 | "get-graphql-schema": "^2.1.2",
39 | "jquery": "^3.6.0",
40 | "moment": "^2.29.1",
41 | "prismjs": "^1.23.0",
42 | "react": "^16.13.1",
43 | "react-bootstrap": "^1.6.0",
44 | "react-codemirror": "^1.0.0",
45 | "react-codemirror2": "^7.2.1",
46 | "react-dom": "^16.13.1",
47 | "react-dropdown": "^1.9.2",
48 | "react-icons": "^4.2.0",
49 | "react-moment": "^1.1.1",
50 | "react-relay": "^11.0.2",
51 | "react-router-dom": "^5.2.0",
52 | "react-scroll": "^1.8.2",
53 | "react-search-autocomplete": "^5.2.2",
54 | "react-spring": "^8.0.27",
55 | "relay-runtime": "^11.0.2",
56 | "uuid": "^8.3.2"
57 | },
58 | "peerDependencies": {
59 | "react-spring": "^8.0.27"
60 | },
61 | "devDependencies": {
62 | "@babel/core": "^7.9.6",
63 | "@babel/preset-env": "^7.9.6",
64 | "@babel/preset-react": "^7.9.4",
65 | "babel-loader": "^8.1.0",
66 | "babel-plugin-relay": "^11.0.2",
67 | "babel-plugin-transform-runtime": "^6.23.0",
68 | "babili-webpack-plugin": "^0.1.2",
69 | "cross-env": "^7.0.2",
70 | "css-loader": "^3.5.3",
71 | "electron": "^9.0.0",
72 | "electron-devtools-installer": "^3.2.0",
73 | "electron-packager": "^14.2.1",
74 | "file-loader": "^6.0.0",
75 | "graphql": "^15.5.0",
76 | "html-webpack-plugin": "^4.3.0",
77 | "mini-css-extract-plugin": "^0.9.0",
78 | "relay-compiler": "^11.0.2",
79 | "style-loader": "^1.2.0",
80 | "webpack": "^4.43.0",
81 | "webpack-cli": "^3.3.11",
82 | "webpack-dev-server": "^3.10.3"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/queryHistory.json:
--------------------------------------------------------------------------------
1 | {
2 | "e1db2dd181b89ce359d0cce00afcaf37{\"id\":15125}": {
3 | "operation": {
4 | "fragment": {
5 | "kind": "SingularReaderSelector",
6 | "dataID": "client:root",
7 | "isWithinUnmatchedTypeRefinement": false,
8 | "node": {
9 | "argumentDefinitions": [
10 | {
11 | "defaultValue": null,
12 | "kind": "LocalArgument",
13 | "name": "id"
14 | }
15 | ],
16 | "kind": "Fragment",
17 | "metadata": null,
18 | "name": "importedQueryQuery",
19 | "selections": [
20 | {
21 | "alias": null,
22 | "args": [
23 | {
24 | "kind": "Variable",
25 | "name": "id",
26 | "variableName": "id"
27 | },
28 | {
29 | "kind": "Literal",
30 | "name": "type",
31 | "value": "ANIME"
32 | }
33 | ],
34 | "concreteType": "Media",
35 | "kind": "LinkedField",
36 | "name": "Media",
37 | "plural": false,
38 | "selections": [
39 | {
40 | "alias": "_id",
41 | "args": null,
42 | "kind": "ScalarField",
43 | "name": "id",
44 | "storageKey": null
45 | },
46 | {
47 | "alias": null,
48 | "args": null,
49 | "concreteType": "MediaTitle",
50 | "kind": "LinkedField",
51 | "name": "title",
52 | "plural": false,
53 | "selections": [
54 | {
55 | "alias": null,
56 | "args": null,
57 | "kind": "ScalarField",
58 | "name": "romaji",
59 | "storageKey": null
60 | },
61 | {
62 | "alias": null,
63 | "args": null,
64 | "kind": "ScalarField",
65 | "name": "english",
66 | "storageKey": null
67 | },
68 | {
69 | "alias": null,
70 | "args": null,
71 | "kind": "ScalarField",
72 | "name": "native",
73 | "storageKey": null
74 | }
75 | ],
76 | "storageKey": null
77 | }
78 | ],
79 | "storageKey": null
80 | }
81 | ],
82 | "type": "Query",
83 | "abstractKey": null
84 | },
85 | "variables": {
86 | "id": 15125
87 | },
88 | "owner": {
89 | "identifier": "e1db2dd181b89ce359d0cce00afcaf37{\"id\":15125}",
90 | "node": {
91 | "fragment": {
92 | "argumentDefinitions": [
93 | {
94 | "defaultValue": null,
95 | "kind": "LocalArgument",
96 | "name": "id"
97 | }
98 | ],
99 | "kind": "Fragment",
100 | "metadata": null,
101 | "name": "importedQueryQuery",
102 | "selections": [
103 | {
104 | "alias": null,
105 | "args": [
106 | {
107 | "kind": "Variable",
108 | "name": "id",
109 | "variableName": "id"
110 | },
111 | {
112 | "kind": "Literal",
113 | "name": "type",
114 | "value": "ANIME"
115 | }
116 | ],
117 | "concreteType": "Media",
118 | "kind": "LinkedField",
119 | "name": "Media",
120 | "plural": false,
121 | "selections": [
122 | {
123 | "alias": "_id",
124 | "args": null,
125 | "kind": "ScalarField",
126 | "name": "id",
127 | "storageKey": null
128 | },
129 | {
130 | "alias": null,
131 | "args": null,
132 | "concreteType": "MediaTitle",
133 | "kind": "LinkedField",
134 | "name": "title",
135 | "plural": false,
136 | "selections": [
137 | {
138 | "alias": null,
139 | "args": null,
140 | "kind": "ScalarField",
141 | "name": "romaji",
142 | "storageKey": null
143 | },
144 | {
145 | "alias": null,
146 | "args": null,
147 | "kind": "ScalarField",
148 | "name": "english",
149 | "storageKey": null
150 | },
151 | {
152 | "alias": null,
153 | "args": null,
154 | "kind": "ScalarField",
155 | "name": "native",
156 | "storageKey": null
157 | }
158 | ],
159 | "storageKey": null
160 | }
161 | ],
162 | "storageKey": null
163 | }
164 | ],
165 | "type": "Query",
166 | "abstractKey": null
167 | },
168 | "kind": "Request",
169 | "operation": {
170 | "argumentDefinitions": [
171 | {
172 | "defaultValue": null,
173 | "kind": "LocalArgument",
174 | "name": "id"
175 | }
176 | ],
177 | "kind": "Operation",
178 | "name": "importedQueryQuery",
179 | "selections": [
180 | {
181 | "alias": null,
182 | "args": [
183 | {
184 | "kind": "Variable",
185 | "name": "id",
186 | "variableName": "id"
187 | },
188 | {
189 | "kind": "Literal",
190 | "name": "type",
191 | "value": "ANIME"
192 | }
193 | ],
194 | "concreteType": "Media",
195 | "kind": "LinkedField",
196 | "name": "Media",
197 | "plural": false,
198 | "selections": [
199 | {
200 | "alias": "_id",
201 | "args": null,
202 | "kind": "ScalarField",
203 | "name": "id",
204 | "storageKey": null
205 | },
206 | {
207 | "alias": null,
208 | "args": null,
209 | "concreteType": "MediaTitle",
210 | "kind": "LinkedField",
211 | "name": "title",
212 | "plural": false,
213 | "selections": [
214 | {
215 | "alias": null,
216 | "args": null,
217 | "kind": "ScalarField",
218 | "name": "romaji",
219 | "storageKey": null
220 | },
221 | {
222 | "alias": null,
223 | "args": null,
224 | "kind": "ScalarField",
225 | "name": "english",
226 | "storageKey": null
227 | },
228 | {
229 | "alias": null,
230 | "args": null,
231 | "kind": "ScalarField",
232 | "name": "native",
233 | "storageKey": null
234 | }
235 | ],
236 | "storageKey": null
237 | }
238 | ],
239 | "storageKey": null
240 | }
241 | ]
242 | },
243 | "params": {
244 | "cacheID": "e1db2dd181b89ce359d0cce00afcaf37",
245 | "id": null,
246 | "metadata": {},
247 | "name": "importedQueryQuery",
248 | "operationKind": "query",
249 | "text": "query importedQueryQuery(\n $id: Int\n) {\n Media(id: $id, type: ANIME) {\n _id: id\n title {\n romaji\n english\n native\n }\n }\n}\n"
250 | },
251 | "hash": "e07846b070fdce2af28b3fcf1f7257a6"
252 | },
253 | "variables": {
254 | "id": 15125
255 | },
256 | "cacheConfig": {
257 | "force": true
258 | }
259 | }
260 | },
261 | "request": {
262 | "identifier": "e1db2dd181b89ce359d0cce00afcaf37{\"id\":15125}",
263 | "node": {
264 | "fragment": {
265 | "argumentDefinitions": [
266 | {
267 | "defaultValue": null,
268 | "kind": "LocalArgument",
269 | "name": "id"
270 | }
271 | ],
272 | "kind": "Fragment",
273 | "metadata": null,
274 | "name": "importedQueryQuery",
275 | "selections": [
276 | {
277 | "alias": null,
278 | "args": [
279 | {
280 | "kind": "Variable",
281 | "name": "id",
282 | "variableName": "id"
283 | },
284 | {
285 | "kind": "Literal",
286 | "name": "type",
287 | "value": "ANIME"
288 | }
289 | ],
290 | "concreteType": "Media",
291 | "kind": "LinkedField",
292 | "name": "Media",
293 | "plural": false,
294 | "selections": [
295 | {
296 | "alias": "_id",
297 | "args": null,
298 | "kind": "ScalarField",
299 | "name": "id",
300 | "storageKey": null
301 | },
302 | {
303 | "alias": null,
304 | "args": null,
305 | "concreteType": "MediaTitle",
306 | "kind": "LinkedField",
307 | "name": "title",
308 | "plural": false,
309 | "selections": [
310 | {
311 | "alias": null,
312 | "args": null,
313 | "kind": "ScalarField",
314 | "name": "romaji",
315 | "storageKey": null
316 | },
317 | {
318 | "alias": null,
319 | "args": null,
320 | "kind": "ScalarField",
321 | "name": "english",
322 | "storageKey": null
323 | },
324 | {
325 | "alias": null,
326 | "args": null,
327 | "kind": "ScalarField",
328 | "name": "native",
329 | "storageKey": null
330 | }
331 | ],
332 | "storageKey": null
333 | }
334 | ],
335 | "storageKey": null
336 | }
337 | ],
338 | "type": "Query",
339 | "abstractKey": null
340 | },
341 | "kind": "Request",
342 | "operation": {
343 | "argumentDefinitions": [
344 | {
345 | "defaultValue": null,
346 | "kind": "LocalArgument",
347 | "name": "id"
348 | }
349 | ],
350 | "kind": "Operation",
351 | "name": "importedQueryQuery",
352 | "selections": [
353 | {
354 | "alias": null,
355 | "args": [
356 | {
357 | "kind": "Variable",
358 | "name": "id",
359 | "variableName": "id"
360 | },
361 | {
362 | "kind": "Literal",
363 | "name": "type",
364 | "value": "ANIME"
365 | }
366 | ],
367 | "concreteType": "Media",
368 | "kind": "LinkedField",
369 | "name": "Media",
370 | "plural": false,
371 | "selections": [
372 | {
373 | "alias": "_id",
374 | "args": null,
375 | "kind": "ScalarField",
376 | "name": "id",
377 | "storageKey": null
378 | },
379 | {
380 | "alias": null,
381 | "args": null,
382 | "concreteType": "MediaTitle",
383 | "kind": "LinkedField",
384 | "name": "title",
385 | "plural": false,
386 | "selections": [
387 | {
388 | "alias": null,
389 | "args": null,
390 | "kind": "ScalarField",
391 | "name": "romaji",
392 | "storageKey": null
393 | },
394 | {
395 | "alias": null,
396 | "args": null,
397 | "kind": "ScalarField",
398 | "name": "english",
399 | "storageKey": null
400 | },
401 | {
402 | "alias": null,
403 | "args": null,
404 | "kind": "ScalarField",
405 | "name": "native",
406 | "storageKey": null
407 | }
408 | ],
409 | "storageKey": null
410 | }
411 | ],
412 | "storageKey": null
413 | }
414 | ]
415 | },
416 | "params": {
417 | "cacheID": "e1db2dd181b89ce359d0cce00afcaf37",
418 | "id": null,
419 | "metadata": {},
420 | "name": "importedQueryQuery",
421 | "operationKind": "query",
422 | "text": "query importedQueryQuery(\n $id: Int\n) {\n Media(id: $id, type: ANIME) {\n _id: id\n title {\n romaji\n english\n native\n }\n }\n}\n"
423 | },
424 | "hash": "e07846b070fdce2af28b3fcf1f7257a6"
425 | },
426 | "variables": {
427 | "id": 15125
428 | },
429 | "cacheConfig": {
430 | "force": true
431 | }
432 | },
433 | "root": {
434 | "dataID": "client:root",
435 | "node": {
436 | "argumentDefinitions": [
437 | {
438 | "defaultValue": null,
439 | "kind": "LocalArgument",
440 | "name": "id"
441 | }
442 | ],
443 | "kind": "Operation",
444 | "name": "importedQueryQuery",
445 | "selections": [
446 | {
447 | "alias": null,
448 | "args": [
449 | {
450 | "kind": "Variable",
451 | "name": "id",
452 | "variableName": "id"
453 | },
454 | {
455 | "kind": "Literal",
456 | "name": "type",
457 | "value": "ANIME"
458 | }
459 | ],
460 | "concreteType": "Media",
461 | "kind": "LinkedField",
462 | "name": "Media",
463 | "plural": false,
464 | "selections": [
465 | {
466 | "alias": "_id",
467 | "args": null,
468 | "kind": "ScalarField",
469 | "name": "id",
470 | "storageKey": null
471 | },
472 | {
473 | "alias": null,
474 | "args": null,
475 | "concreteType": "MediaTitle",
476 | "kind": "LinkedField",
477 | "name": "title",
478 | "plural": false,
479 | "selections": [
480 | {
481 | "alias": null,
482 | "args": null,
483 | "kind": "ScalarField",
484 | "name": "romaji",
485 | "storageKey": null
486 | },
487 | {
488 | "alias": null,
489 | "args": null,
490 | "kind": "ScalarField",
491 | "name": "english",
492 | "storageKey": null
493 | },
494 | {
495 | "alias": null,
496 | "args": null,
497 | "kind": "ScalarField",
498 | "name": "native",
499 | "storageKey": null
500 | }
501 | ],
502 | "storageKey": null
503 | }
504 | ],
505 | "storageKey": null
506 | }
507 | ]
508 | },
509 | "variables": {
510 | "id": 15125
511 | }
512 | }
513 | },
514 | "refCount": 1,
515 | "epoch": 1,
516 | "fetchTime": 1622661888940
517 | }
518 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | /*
2 | top-level component responsible for rendering all other major components,
3 | */
4 |
5 | import React, { useState, Suspense } from 'react';
6 |
7 | /* COMPONENTS */
8 | import ImportedMode from './components/ImportedMode';
9 | import Footer from './components/Footer';
10 | import SchemaDisplayContainer from './components/SchemaDisplay/SchemaDisplayContainer';
11 | import WrittenResponseDisplay from './components/ResponseDisplay/WrittenResponseDisplay';
12 | import QueryContainer from './components/QueryContainer';
13 | import VariableInput from './components/VariableInput';
14 | import ErrorBoundary from './components/ErrorBoundary';
15 |
16 | /* STYLES */
17 | import Container from 'react-bootstrap/Container';
18 | import Card from 'react-bootstrap/Card';
19 | import Row from 'react-bootstrap/Row';
20 | import Col from 'react-bootstrap/Col';
21 | import './styles/App.css';
22 | import './styles/styles.css';
23 |
24 | /* UTILITIES */
25 | import gqlEndpoint from './relay/gqlendpoint';
26 | import db from './database/db';
27 |
28 | const App = () => {
29 | const [response, setResponse] = useState('');
30 | const [query, setQuery] = useState('');
31 | const [variables, setVariables] = useState('');
32 |
33 | // Define the config we'll need for our Api request
34 | let url = gqlEndpoint.url;
35 | let options = {
36 | method: 'POST',
37 | headers: {
38 | 'Content-Type': 'application/json',
39 | 'Accept': 'application/json',
40 | },
41 | body: JSON.stringify({
42 | query: query,
43 | variables: variables
44 | })
45 | };
46 |
47 | // Make the HTTP Api request
48 | const submitTypedQuery = () => {
49 | fetch(url, options)
50 | .then(handleResponse)
51 | .then(handleData)
52 | .catch(handleError);
53 | db.addQuery(query);
54 | }
55 |
56 | //Helper functions for submitTypedQuery:
57 | function handleResponse(response) {
58 | return response.json().then(function (json) {
59 | return response.ok ? json : Promise.reject(json);
60 | });
61 | }
62 | function handleData(data) {
63 | setResponse(data.data);
64 | }
65 | function handleError(error) {
66 | console.error(error);
67 | }
68 |
69 | return (
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | )
136 | }
137 |
138 | export default App;
--------------------------------------------------------------------------------
/src/__generated__/AppQuery.graphql.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @flow
3 | */
4 |
5 | /* eslint-disable */
6 |
7 | 'use strict';
8 |
9 | /*::
10 | import type { ConcreteRequest } from 'relay-runtime';
11 | export type AppQueryVariables = {|
12 | id?: ?number
13 | |};
14 | export type AppQueryResponse = {|
15 | +Media: ?{|
16 | +_id: number,
17 | +title: ?{|
18 | +romaji: ?string,
19 | +english: ?string,
20 | +native: ?string,
21 | |},
22 | |}
23 | |};
24 | export type AppQuery = {|
25 | variables: AppQueryVariables,
26 | response: AppQueryResponse,
27 | |};
28 | */
29 |
30 |
31 | /*
32 | query AppQuery(
33 | $id: Int
34 | ) {
35 | Media(id: $id, type: ANIME) {
36 | _id: id
37 | title {
38 | romaji
39 | english
40 | native
41 | }
42 | }
43 | }
44 | */
45 |
46 | const node/*: ConcreteRequest*/ = (function(){
47 | var v0 = [
48 | {
49 | "defaultValue": null,
50 | "kind": "LocalArgument",
51 | "name": "id"
52 | }
53 | ],
54 | v1 = [
55 | {
56 | "alias": null,
57 | "args": [
58 | {
59 | "kind": "Variable",
60 | "name": "id",
61 | "variableName": "id"
62 | },
63 | {
64 | "kind": "Literal",
65 | "name": "type",
66 | "value": "ANIME"
67 | }
68 | ],
69 | "concreteType": "Media",
70 | "kind": "LinkedField",
71 | "name": "Media",
72 | "plural": false,
73 | "selections": [
74 | {
75 | "alias": "_id",
76 | "args": null,
77 | "kind": "ScalarField",
78 | "name": "id",
79 | "storageKey": null
80 | },
81 | {
82 | "alias": null,
83 | "args": null,
84 | "concreteType": "MediaTitle",
85 | "kind": "LinkedField",
86 | "name": "title",
87 | "plural": false,
88 | "selections": [
89 | {
90 | "alias": null,
91 | "args": null,
92 | "kind": "ScalarField",
93 | "name": "romaji",
94 | "storageKey": null
95 | },
96 | {
97 | "alias": null,
98 | "args": null,
99 | "kind": "ScalarField",
100 | "name": "english",
101 | "storageKey": null
102 | },
103 | {
104 | "alias": null,
105 | "args": null,
106 | "kind": "ScalarField",
107 | "name": "native",
108 | "storageKey": null
109 | }
110 | ],
111 | "storageKey": null
112 | }
113 | ],
114 | "storageKey": null
115 | }
116 | ];
117 | return {
118 | "fragment": {
119 | "argumentDefinitions": (v0/*: any*/),
120 | "kind": "Fragment",
121 | "metadata": null,
122 | "name": "AppQuery",
123 | "selections": (v1/*: any*/),
124 | "type": "Query",
125 | "abstractKey": null
126 | },
127 | "kind": "Request",
128 | "operation": {
129 | "argumentDefinitions": (v0/*: any*/),
130 | "kind": "Operation",
131 | "name": "AppQuery",
132 | "selections": (v1/*: any*/)
133 | },
134 | "params": {
135 | "cacheID": "7f3dd2b1a3932911e6ec5b9bb56f7d16",
136 | "id": null,
137 | "metadata": {},
138 | "name": "AppQuery",
139 | "operationKind": "query",
140 | "text": "query AppQuery(\n $id: Int\n) {\n Media(id: $id, type: ANIME) {\n _id: id\n title {\n romaji\n english\n native\n }\n }\n}\n"
141 | }
142 | };
143 | })();
144 | // prettier-ignore
145 | (node/*: any*/).hash = 'd742635e8d68a49d2374d004cf9c95cf';
146 |
147 | module.exports = node;
148 |
--------------------------------------------------------------------------------
/src/components/Downloader/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import "../../styles/downloader.css";
3 | import { ProgressBar } from "react-bootstrap";
4 | import Axios from "axios";
5 |
6 | const Downloader = ({ files = [], remove }) => {
7 | return (
8 |
9 |
10 |
File Downloader
11 |
12 | {files.map((file, idx) => (
13 | remove(file.downloadId)}
16 | {...file}
17 | />
18 | ))}
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | // Later will fill this boilerplate with the current download information
26 | const DownloadItem = ({ name, file, filename, removeFile }) => {
27 | const [downloadInfo, setDownloadInfo] = useState({
28 | progress: 0,
29 | completed: false,
30 | total: 0,
31 | loaded: 0,
32 | });
33 |
34 | // Side effect, to show ho much of the download progress remains
35 | useEffect(() => {
36 | const options = {
37 | onDownloadProgress: (progressEvent) => {
38 | const { loaded, total } = progressEvent;
39 |
40 | setDownloadInfo({
41 | progress: Math.floor((loaded * 100) / total),
42 | loaded,
43 | total,
44 | completed: false,
45 | });
46 | },
47 | };
48 |
49 | // Using Axios built-in Blob class to display the download information
50 | Axios.get(file, {
51 | responseType: "blob",
52 | ...options,
53 | }).then(function (response) {
54 | // console.log(response);
55 |
56 | const url = window.URL.createObjectURL(
57 | new Blob([response.data], {
58 | type: response.headers["content-type"],
59 | })
60 | );
61 |
62 | const link = document.createElement("a");
63 | link.href = url;
64 | link.setAttribute("download", filename);
65 | document.body.appendChild(link);
66 | link.click();
67 |
68 | // displays info on lines 27-32 boilerplate
69 | setDownloadInfo((info) => ({
70 | ...info,
71 | completed: true,
72 | }));
73 |
74 | // takes the file out of the blurb after 4 seconds
75 | setTimeout(() => {
76 | removeFile();
77 | }, 4000);
78 | });
79 | }, []);
80 |
81 | const formatBytes = (bytes) => `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
82 |
83 | return (
84 |
85 |
86 |
87 |
{name}
88 |
89 |
90 | {downloadInfo.loaded > 0 && (
91 | <>
92 |
93 | {formatBytes(downloadInfo.loaded)}
94 |
95 | / {formatBytes(downloadInfo.total)}
96 | >
97 | )}
98 |
99 | {downloadInfo.loaded === 0 && <>Initializing...>}
100 |
101 |
102 |
103 | {downloadInfo.completed && (
104 |
105 | Completed
106 |
107 | )}
108 |
109 |
110 |
118 |
119 |
120 | );
121 | };
122 |
123 | export default Downloader;
124 |
--------------------------------------------------------------------------------
/src/components/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class ErrorBoundary extends Component {
4 |
5 | constructor(props) {
6 | super(props);
7 | this.state = { hasError: false };
8 | };
9 |
10 | static getDerivedStateFromError(error) {
11 | return { hasError: true };
12 | };
13 |
14 | componentDidCatch(error, errorInfo) {
15 | console.error(error, errorInfo);
16 | };
17 |
18 | render() {
19 | if (this.state.hasError) {
20 | return Something went wrong. ;
21 | }
22 | return this.props.children;
23 | }
24 | };
25 |
26 | export default ErrorBoundary;
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './../styles/App.css';
3 | import { Link, animateScroll as scroll } from "react-scroll";
4 | import Button from 'react-bootstrap/Button';
5 |
6 | const Footer = () => {
7 | return (
8 |
9 |
10 |
18 |
19 | ⇧ Peach Mode
20 |
21 |
22 |
23 |
24 |
25 | PeachQE - Electron App
26 |
27 |
28 |
29 |
30 | Relay Proper ⇩
31 |
32 |
33 |
34 | )
35 | }
36 |
37 | export default Footer
38 |
39 |
--------------------------------------------------------------------------------
/src/components/ImportedMode/TestUpload.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from 'react-bootstrap/Button';
3 | import uploaderHook from '../hooks/uploaderHook';
4 | const electron = window.require('electron');
5 | const {shell} = window.require('electron');
6 | const remote = electron.remote;
7 | const {dialog} = remote;
8 | import * as fs from 'fs';
9 |
10 |
11 | const TestUpload = () => {
12 | return (
13 | <>
14 |
15 |
16 |
17 |
18 | {
24 | dialog.showOpenDialog(function (fileNames) {
25 | if(fileNames === undefined){
26 | console.log("No file selected");
27 | }else{
28 | document.getElementById("actual-file").value = fileNames[0];
29 | uploaderHook.readFile(fileNames[0]);
30 | }
31 | });
32 | }
33 | }
34 | >
35 | Select
36 |
37 |
38 |
39 |
40 |
{
45 | var actualFilePath = document.getElementById("actual-file").value;
46 |
47 | if(actualFilePath){
48 | uploaderHook.saveChanges(actualFilePath,document.getElementById('content-editor').value);
49 | }else{
50 | alert("Please select a file first");
51 | }
52 | }
53 | }
54 | >
55 | Save changes
56 |
57 |
{
62 | var actualFilePath = document.getElementById("actual-file").value;
63 |
64 | if(actualFilePath){
65 | uploaderHook.deleteFile(actualFilePath);
66 | document.getElementById("actual-file").value = "";
67 | document.getElementById("content-editor").value = "";
68 | }else{
69 | alert("Please select a file first");
70 | }
71 | }
72 | }
73 | >
74 | Delete file
75 |
76 |
77 |
78 |
79 |
80 | The file content will be the same as the editor.
81 |
82 |
{
87 | var content = document.getElementById("content-editor").value;
88 |
89 | dialog.showSaveDialog(function (fileName) {
90 | if (fileName === undefined){
91 | console.log("You didn't save the file");
92 | return;
93 | }
94 |
95 | fs.writeFile(fileName, content, function (err) {
96 | if(err){
97 | alert("An error ocurred creating the file "+ err.message)
98 | }
99 |
100 | alert("The file has been succesfully saved");
101 | });
102 | });
103 | }
104 | }
105 | >
106 | Create a new File
107 |
108 |
109 |
110 | >
111 | )
112 | }
113 |
114 | export default TestUpload;
115 |
--------------------------------------------------------------------------------
/src/components/ImportedMode/Uploader.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Button from 'react-bootstrap/Button';
3 | import "bootstrap/dist/css/bootstrap.min.css";
4 | import "./styles/uploader.css";
5 |
6 | const electron = window.require('electron');
7 | const remote = electron.remote
8 |
9 | //require in exec to run terminal commands in js:
10 | const execSync = require('child_process').execSync;
11 |
12 | const Uploader = () => {
13 | const [isShown, setIsShown] = useState(false);
14 |
15 | return (
16 | <>
17 | setIsShown(true)}
21 | onMouseLeave={() => setIsShown(false)}
22 | >
23 |
24 |
To Upload Files...
25 | {isShown && (
26 |
27 | ...please use the Peaches menu to
28 | Start a New Peach . Then
29 | execSync('npm run relay', { encoding: 'utf-8' })}
31 | type='submit'
32 | variant='danger'
33 | size="sm"
34 | >
35 | Restart Relay
36 |
37 |
38 | )}
39 |
40 |
41 | >
42 | )
43 | }
44 |
45 | export default Uploader;
46 |
--------------------------------------------------------------------------------
/src/components/ImportedMode/index.js:
--------------------------------------------------------------------------------
1 | // This file has an OPEN DIALOGUE mock up to access the fs directly.
2 | // We might (or not) need this later
3 |
4 | /* REACT COMPONENTS */
5 | import React, { useState } from 'react';
6 | import VariableInput from '../VariableInput';
7 | import Navbar from '../Navbar';
8 | import QuerySelector from '../QuerySelector';
9 | import ResponseDisplay from '../ResponseDisplay/ImportedResponseDisplay';
10 | import EditorDisplay from '../QueryEditor';
11 | import Uploader from './Uploader';
12 | import StoreDisplay from '../StoreDisplay/StoreDisplay';
13 |
14 | /* RELAY */
15 | import { useQueryLoader } from 'react-relay';
16 | import writtenQuery from '../../relay/__generated__/writtenQuery.graphql'
17 |
18 | /* STYLES */
19 | import Container from 'react-bootstrap/Container';
20 | import Card from 'react-bootstrap/Card';
21 | import Row from 'react-bootstrap/Row';
22 | import Col from 'react-bootstrap/Col';
23 | import Button from 'react-bootstrap/Button';
24 |
25 | const ImportedMode = () =>{
26 | const [renderQuerySelector, setRenderQuerySelector] = useState(false);
27 | const [queryToLoad, setQueryToLoad] = useState(writtenQuery);
28 | const [variables, setVariables] = useState('{"id": 15125}');
29 | const [initialQueryReference, loadQuery, disposeQuery] = useQueryLoader(queryToLoad);
30 |
31 | return(
32 | <>
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 | Store Display
56 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | New Query selector
67 | setRenderQuerySelector(true)}>
73 | Render Query Selector
74 |
75 | {renderQuerySelector && }
81 |
82 |
83 | Editor
84 |
87 |
88 |
89 |
90 |
91 |
92 |
97 |
98 |
99 |
100 |
101 |
102 | >
103 | )
104 | };
105 |
106 | export default ImportedMode;
--------------------------------------------------------------------------------
/src/components/ImportedMode/renderer.js:
--------------------------------------------------------------------------------
1 | const electron = require('electron');
2 | const remote = electron.remote
3 | const ipcRenderer = electron.ipcRenderer;
4 | var $ = require('jquery');
5 |
6 | var dragFile= document.getElementById("drag-file");
7 | dragFile.addEventListener('drop', function (e) {
8 | e.preventDefault();
9 | e.stopPropagation();
10 |
11 | for (let f of e.dataTransfer.files) {
12 | console.log('The file(s) you dragged: ', f)
13 | ipcRenderer.send('ondragstart', f.path)
14 | }
15 | });
16 |
17 | dragFile.addEventListener('dragover', function (e) {
18 | e.preventDefault();
19 | e.stopPropagation();
20 | });
21 |
22 |
23 | $('#btn').on('click', () => {
24 | let txtarea=$('#txtarea').val()
25 | ipcRenderer.send('clickedbutton', txtarea)
26 | // console.log('The file(s) you want to save: ', txtarea)
27 | })
28 |
29 | ipcRenderer.on('fileData', (event, data) => {
30 | $('#txtarea').text(data);
31 | })
32 |
33 | var win = remote.BrowserWindow.getFocusedWindow();
34 | var quit = document.querySelector("#closer");
35 | quit.addEventListener("click", () => {
36 | win.close();
37 | });
--------------------------------------------------------------------------------
/src/components/ImportedMode/styles/uploader.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | /* Create two equal columns that floats next to each other */
6 | .column1 {
7 | float: left;
8 | border-right: 1px solid rgb(88, 88, 88);
9 | width: 30%;
10 | padding: 10px;
11 | height: auto;
12 | }
13 |
14 | .column2 {
15 | /* float: center; */
16 | width: 70%;
17 | padding: 10px;
18 | height: auto;
19 | margin-left: -30px;
20 | /* border: black 1px solid; */
21 | }
22 |
23 | .row {
24 | display: flex;
25 | }
26 |
27 | /* Clear floats after the columns */
28 | .row:after {
29 | content: "";
30 | display: table;
31 | clear: both;
32 | }
33 |
34 | .container2:hover {
35 | background-color: rgba(243, 174, 157, 0.664);
36 | height: 101%;
37 | border-radius: 5px;
38 | box-shadow: 2px 2px 2px rgba(121, 120, 120, 0.87);
39 | }
40 |
41 | .form-control {
42 | width:650px;
43 | height:350px;
44 | margin-left: -30px;
45 | align-content: center;
46 | }
47 |
48 | .popup {
49 | margin-left: 15px;
50 | text-align: left;
51 | }
52 |
53 | .jumbotron12 {
54 | margin-left: 2.5rem;
55 | width: 90%;
56 | height: 100px;
57 | background-color: #ff7f50;
58 | display: flex;
59 | align-items: center;
60 | justify-content: center;
61 | border: 3px;
62 | border-radius: 5px 5px 0 0;
63 | }
64 |
65 | .steps {
66 | clear: both;
67 | list-style: none;
68 | padding-left: 7%;
69 | }
70 | .steps li {
71 | margin: 1.5em 0;
72 | /* padding-top: 1em; */
73 | padding-left: 1em;
74 | display: block;
75 | position: relative;
76 | counter-increment: inst;
77 | }
78 | .steps li::before {
79 | content: counter(inst);
80 |
81 | background: rgba(255, 150, 0, 0.35);
82 | color: #fff;
83 |
84 | font-size: 1em;
85 | font-weight: 700;
86 | font-style: italic;
87 | text-shadow: 1px 1px rgba(255, 150, 0, 0.5);
88 |
89 | border-radius: 0 0.675em 0.675em 0;
90 | font-size: 1.5em;
91 | text-align: center;
92 |
93 | padding-top: 0;
94 | padding-left: 2.25%;
95 | left: -11%;
96 | top: -0.65em;
97 | height: 1.35em;
98 | width: 1.35em;
99 | position: absolute;
100 |
101 | transition: all 0.2s ease-in-out;
102 |
103 | z-index: -1;
104 | }
105 |
106 | @media (min-width: 33em) {
107 | .steps li:before {
108 | border-radius: 50%;
109 | font-size: 1.5em;
110 | height: 1.35em;
111 | margin-left: 2.5%;
112 | padding-left: 0;
113 | padding-top: 0;
114 | top: -0.15em;
115 | width: 1.35em;
116 | z-index: -1;
117 | }
118 | }
119 |
120 | .tooltip {
121 | position: relative;
122 | display: inline-block;
123 | border-bottom: 1px dotted black;
124 | }
125 |
126 | .tooltip .tooltiptext {
127 | visibility: hidden;
128 | width: 340px;
129 | padding: 20px;
130 | background-color: #555;
131 | color: #fff;
132 | text-align: left;
133 | border-radius: 6px;
134 | padding: 5px 0;
135 | position: absolute;
136 | z-index: 1;
137 | bottom: 125%;
138 | left: 0;
139 | margin-left: -60px;
140 | opacity: 0;
141 | transition: opacity 0.3s;
142 | }
143 |
144 | .tooltip .tooltiptext::after {
145 | content: "";
146 | position: absolute;
147 | top: 100%;
148 | left: 50%;
149 | margin-left: -5px;
150 | border-width: 5px;
151 | border-style: solid;
152 | border-color: #555 transparent transparent transparent;
153 | }
154 |
155 | .tooltip:hover .tooltiptext {
156 | visibility: visible;
157 | opacity: 1;
158 | }
--------------------------------------------------------------------------------
/src/components/ImportedMode/upload-files.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import UploadService from "../hooks/upload-files.service";
3 | export default class UploadFiles extends Component {
4 | constructor(props) {
5 | super(props);
6 |
7 | this.state = {
8 | selectedFiles: undefined,
9 | currentFile: undefined,
10 | progress: 0,
11 | message: "",
12 |
13 | fileInfos: [],
14 | };
15 | }
16 |
17 | selectFile(event) {
18 | this.setState({
19 | selectedFiles: event.target.files,
20 | });
21 | }
22 |
23 | upload() {
24 | let currentFile = this.state.selectedFiles[0];
25 |
26 | this.setState({
27 | progress: 0,
28 | currentFile: currentFile,
29 | });
30 |
31 | UploadService.upload(currentFile, (event) => {
32 | this.setState({
33 | progress: Math.round((100 * event.loaded) / event.total),
34 | });
35 | })
36 | .then((response) => {
37 | this.setState({
38 | message: response.data.message,
39 | });
40 | return UploadService.getFiles();
41 | })
42 | .then((files) => {
43 | this.setState({
44 | fileInfos: files.data,
45 | });
46 | })
47 | .catch(() => {
48 | this.setState({
49 | progress: 0,
50 | message: "Could not upload the file!",
51 | currentFile: undefined,
52 | });
53 | });
54 |
55 | this.setState({
56 | selectedFiles: undefined,
57 | });
58 | }
59 |
60 | render() {
61 | const {
62 | selectedFiles,
63 | currentFile,
64 | progress,
65 | message,
66 | fileInfos,
67 | } = this.state;
68 |
69 | return (
70 |
71 | {currentFile && (
72 |
73 |
81 | {progress}%
82 |
83 |
84 | )}
85 |
86 |
87 |
88 |
89 |
90 |
94 | Upload
95 |
96 |
97 |
98 | {message}
99 |
100 |
101 |
102 |
List of Files
103 |
104 | {fileInfos &&
105 | fileInfos.map((file, index) => (
106 |
107 | {file.name}
108 |
109 | ))}
110 |
111 |
112 |
113 | );
114 | }
115 | }
--------------------------------------------------------------------------------
/src/components/ImportedMode/watcher.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | (async () => {
4 | const watcher = fs.watch('../../relay/imported.js');
5 | watcher.on('change', () => {
6 | console.log('changed')
7 | });
8 | })();
--------------------------------------------------------------------------------
/src/components/Logo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from '../../assets/PeachQE-3.png';
3 |
4 | const Logo = () => {
5 | return (
6 |
7 |
8 |
9 |
10 | Loading...
11 |
12 | )
13 | }
14 |
15 | export default Logo;
16 |
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './../styles/App.css';
3 | import { Link, animateScroll as scroll } from "react-scroll";
4 | import Button from 'react-bootstrap/Button';
5 |
6 | const Navbar = () => {
7 | return (
8 |
9 |
10 |
11 | ⇧ Peach Mode
12 |
13 |
14 |
15 |
16 | PeachQE - Electron App
17 |
18 |
19 |
20 |
29 |
30 | Relay Proper ⇩
31 |
32 |
33 |
34 |
35 | )
36 | };
37 |
38 | export default Navbar;
--------------------------------------------------------------------------------
/src/components/QueryContainer/History.js:
--------------------------------------------------------------------------------
1 | /*
2 | renders information about query history from the local database in a
3 | drop-down menu, indexed by timestamp and query text
4 | */
5 |
6 | import React, { useState } from 'react';
7 | import Dropdown from 'react-dropdown';
8 | import 'react-dropdown/style.css';
9 | import db from '../../database/db.js';
10 |
11 | const History = ({setQuery}) => {
12 |
13 | const [history, setHistory] = useState(db.getQueryHistory());
14 | const trimmedHistory = history.map(historyObject => {
15 | const optionObject = {};
16 | optionObject.label = historyObject.timeStamp
17 | + '\n'
18 | + historyObject.queryText.slice(0, 25)
19 | + '...';
20 | optionObject.value = historyObject.queryText;
21 | return optionObject;
22 | });
23 |
24 | const reloadHistory = (queryText) => {
25 | setQuery(queryText);
26 | };
27 |
28 | return (
29 | reloadHistory(e.value)}
34 | />
35 | )
36 | }
37 |
38 | export default History;
--------------------------------------------------------------------------------
/src/components/QueryContainer/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | stateful component updates query string to be run in App.js via fs module,
3 | also reruns relay compiler when the query is submitted
4 | */
5 |
6 | import React from 'react';
7 | import Container from 'react-bootstrap/Container';
8 | import Button from 'react-bootstrap/Button';
9 | import '../../styles/styles.css';
10 | import History from './History';
11 |
12 | //importing library for code editor
13 | import 'codemirror/lib/codemirror.css';
14 | import 'codemirror/mode/javascript/javascript';
15 | import { Controlled as ControlledEditor } from 'react-codemirror2';
16 |
17 | const QueryContainer = ({submitTypedQuery, query, setQuery}) => {
18 |
19 | const updateQueryText = (editor, data, value) => {
20 | setQuery(value);
21 | }
22 |
23 | return (
24 |
25 |
26 |
29 |
41 | submitTypedQuery()}
43 | type='submit'
44 | variant='secondary'
45 | className='mb-3'
46 | >
47 | Submit Query
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | export default QueryContainer;
--------------------------------------------------------------------------------
/src/components/QueryEditor/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Button from 'react-bootstrap/Button';
3 | import fs from 'fs';
4 | import path from 'path';
5 |
6 | //importing library for code editor
7 | import 'codemirror/lib/codemirror.css';
8 | import 'codemirror/mode/javascript/javascript';
9 | import { Controlled as ControlledEditor } from 'react-codemirror2';
10 |
11 | //require in exec to run terminal commands in js:
12 | const execSync = require('child_process').execSync;
13 |
14 | const EditorDisplay = ({setRenderQuerySelector}) => {
15 |
16 | const importedPath = path.resolve('./src/relay/imported.js')
17 | const [ editorText, setEditorText ] = useState(fs.readFileSync(importedPath, 'utf8'));
18 | const options = {
19 | lineWrapping: true,
20 | lint: true,
21 | mode: {
22 | name: 'javascript',
23 | json: true
24 | },
25 | lineNumbers: true,
26 | theme: 'default height18rem readonly',
27 | };
28 |
29 | const updateEditorText = (editor, data, value) => {
30 | setEditorText(value);
31 | }
32 |
33 | const saveToImported = (newQueryText) => {
34 | fs.writeFileSync(path.resolve(importedPath), newQueryText);
35 | execSync('npm run relay', { encoding: 'utf-8' });
36 | }
37 |
38 | return (
39 |
40 |
45 |
46 | {
50 | saveToImported(editorText);
51 | setRenderQuerySelector(false);
52 | }}>Save Edited Query
53 |
54 |
55 | )
56 | };
57 |
58 | export default EditorDisplay;
59 |
60 |
--------------------------------------------------------------------------------
/src/components/QuerySelector/QueryButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from 'react-bootstrap/Button';
3 |
4 | const QueryButton = ({query, setQueryToLoad, loadQuery, variables}) => {
5 |
6 | return (
7 |
8 | {
13 | setQueryToLoad(query);
14 | loadQuery(JSON.parse(variables));
15 | }}>
16 | {query.params.text}
17 |
18 |
19 | )
20 | }
21 |
22 | export default QueryButton;
--------------------------------------------------------------------------------
/src/components/QuerySelector/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | functional component renders a selection of loaded queries
3 | and populates the QueryContainer with the selected queries text
4 | */
5 |
6 | import React, { useState, useEffect } from 'react';
7 | import * as importedQueries from '../../relay/__generated__';
8 | import QueryButton from './QueryButton';
9 | import Button from 'react-bootstrap/Button';
10 | import db from '../../database/db';
11 |
12 | const QuerySelector = ({queryToLoad, setQueryKey, setQueryToLoad, loadQuery, variables,}) => {
13 | const [queryButtons, setQueryButtons] = useState([]);
14 |
15 | useEffect(() => {
16 | const queryButtonDetails = [];
17 | if (importedQueries){
18 | for (let query in importedQueries) {
19 | queryButtonDetails.push(importedQueries[query])
20 | }
21 | };
22 | setQueryButtons(queryButtonDetails || []);
23 | }, [queryToLoad, importedQueries]);
24 |
25 | return (
26 |
27 | {queryButtons.map(query => {
28 | return (
29 | )
36 | })}
37 | queryButtons.forEach(query => db.addQuery(query.params.text))}
42 | >
43 | Save to History
44 |
45 |
46 | )
47 | };
48 |
49 | export default QuerySelector;
--------------------------------------------------------------------------------
/src/components/ResponseDisplay/ImportedResponseDisplay.js:
--------------------------------------------------------------------------------
1 | /*
2 | stateless component renders query response data passed from imported.js
3 | via the ImportedMode component. Note that this is not the same as the
4 | WrittenResponseDisplay which renders queries input into the written.js
5 | file via the QueryEditor
6 | */
7 |
8 | import React, {Suspense} from 'react';
9 | import Container from 'react-bootstrap/Container';
10 | import '../../styles/styles.css';
11 | import Response from './Response';
12 |
13 | const ResponseDisplay = ({initialQueryReference, queryToLoad, variables}) => {
14 |
15 | return (
16 |
17 |
18 |
19 | Response
20 |
21 |
22 | {initialQueryReference && (
23 |
27 | )}
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default ResponseDisplay;
--------------------------------------------------------------------------------
/src/components/ResponseDisplay/Response.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { usePreloadedQuery } from 'react-relay';
3 |
4 | const Response = ({queryToLoad, initialQueryReference}) => {
5 | let data;
6 | data = usePreloadedQuery(queryToLoad, initialQueryReference);
7 | return (
8 |
9 |
10 | {data && JSON.stringify(data, null, 2).replace(/"/g, '')}
11 |
12 |
13 | )
14 | };
15 |
16 | export default Response;
--------------------------------------------------------------------------------
/src/components/ResponseDisplay/WrittenResponseDisplay.js:
--------------------------------------------------------------------------------
1 | /*
2 | stateless component renders query response data passed from App.js
3 | */
4 |
5 | import React from 'react';
6 | import Container from 'react-bootstrap/Container';
7 | import '../../styles/styles.css';
8 |
9 | const WrittenResponseDisplay = ({response}) => {
10 |
11 | //Stringifies result and removes double quotes
12 | const format = (response) => {
13 | const output = JSON.stringify(response, null, 2);
14 | return output.replace(/"/g, '');
15 | }
16 |
17 | return (
18 |
19 |
20 |
21 | Response
22 |
23 |
27 | {response ? format(response) : ''}
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default WrittenResponseDisplay;
--------------------------------------------------------------------------------
/src/components/SchemaDisplay/SchemaDisplayContainer.js:
--------------------------------------------------------------------------------
1 | /*
2 | stateful functional container component responsible for rendering
3 | as many SchemaDisplay components as the uploaded Schema require
4 | */
5 |
6 | import React, { useState } from 'react';
7 | import SchemaDisplay from '.';
8 | import Container from 'react-bootstrap/Container';
9 | import makeJsonSchema from '../../relay/makeJsonSchema';
10 | import Button from 'react-bootstrap/Button';
11 | import InputGqlSchema from '../SchemaDownload/InputGqlSchema';
12 | import { ReactSearchAutocomplete } from 'react-search-autocomplete'
13 |
14 | // calls makeJsonSchema to generate a js array of objects we can render from schema.graphql
15 | const jsonSchema = makeJsonSchema();
16 |
17 | const SchemaDisplayContainer = () => {
18 | const [focus, setFocus] = useState(0);
19 | const [schemaList, setSchemaList] = useState([jsonSchema]);
20 | const items = []
21 |
22 | for (let i = 0; i < schemaList[0].length; i++) {
23 | items.push({
24 | id: i,
25 | name: schemaList[0][i].name,
26 | key: i
27 | });
28 | }
29 |
30 | const handleClick = () => {
31 | setFocus(0)
32 | }
33 |
34 | const handleOnSearch = (string, schema) => {
35 | if (string.toLowerCase() === 'show all'){
36 | console.log('Triggered')
37 | setFocus(0)
38 | }
39 | }
40 |
41 | const handleOnSelect = (schema) => {
42 | setFocus(schema.id)
43 | }
44 |
45 | let display = {};
46 | if (!focus) {
47 | display = jsonSchema.map(schema => {
48 | return
53 | });
54 | } else {
55 | display =
61 | };
62 |
63 | return (
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
Schema Search
72 |
77 | Show All
78 |
79 |
80 |
86 |
87 | )
88 | }
89 |
90 | export default SchemaDisplayContainer;
91 |
--------------------------------------------------------------------------------
/src/components/SchemaDisplay/SchemaSearch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ReactSearchAutocomplete } from 'react-search-autocomplete'
3 | const SchemaSearch = ({ schemaList }) => {
4 | const items = []
5 | for(let i = 0; i < schemaList.length; i++){
6 | items.push(
7 | {id: i,
8 | name: schemaList[i].name,
9 | key: i
10 | });
11 | }
12 |
13 | return (
14 |
24 | )
25 | };
26 |
27 | export default SchemaSearch
--------------------------------------------------------------------------------
/src/components/SchemaDisplay/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | stateless functional presentation component solely
3 | responsible for displaying a single Schema within
4 | the Schema Display Container
5 | */
6 |
7 | import React from 'react';
8 |
9 | const SchemaDisplay = ({schemaName, schemaFields, ref}) => {
10 |
11 | return (
12 |
13 | {schemaName}
14 | {schemaFields.map(field => {field.type}
)}
15 |
16 | )
17 | }
18 |
19 | export default SchemaDisplay;
20 |
--------------------------------------------------------------------------------
/src/components/SchemaDownload/FileDownloader.js:
--------------------------------------------------------------------------------
1 | /*
2 | iterates over the schemaHistory.json to generate buttons that will reload the appropriate schema
3 | */
4 |
5 | /* eslint-disable jsx-a11y/anchor-is-valid */
6 | import React, { useState } from 'react';
7 | import useFileDownloader from "../hooks/useFileDownloader";
8 | import gqlEndpoint from '../../relay/gqlEndpoint';
9 | import schemaHistory from '../../database/schemaHistory.json';
10 | import db from '../../database/db';
11 |
12 | const execSync = require('child_process').execSync;
13 |
14 | const FileDownloader = () => {
15 | const [downloadFile, downloaderComponentUI] = useFileDownloader();
16 | // const download = (file) => downloadFile(file);
17 | const [schemaName, setSchemaName] = useState(gqlEndpoint.url);
18 |
19 | // set Schema name
20 | const handleClick = (schema) => {
21 | setSchemaName(schema.name);
22 | gqlEndpoint.url = schema.url;
23 | db.addURL(schema.name);
24 | execSync(`get-graphql-schema ${schema.url} > schema.graphql`, { encoding: 'utf-8' });
25 | }
26 |
27 | return (
28 | <>
29 |
30 |
31 |
34 |
35 | {schemaHistory.map((schema, idx) => (
36 |
53 | ))}
54 |
55 |
56 | {downloaderComponentUI}
57 |
58 | >
59 | );
60 | };
61 |
62 | export default FileDownloader;
63 |
--------------------------------------------------------------------------------
/src/components/SchemaDownload/InputGqlSchema.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Container from 'react-bootstrap/Container';
3 | import Button from 'react-bootstrap/Button';
4 | import Modal from './Modal';
5 | import gqlEndpoint from '../../relay/gqlEndpoint';
6 |
7 | const InputGqlSchema = () => {
8 | const [showModal, setShowModal] = useState(false);
9 | const [schemaName, setSchemaName] = useState(gqlEndpoint.url);
10 |
11 |
12 | // TODO: This opens the modal
13 | const openModal = () => {
14 | setShowModal(prev => !prev);
15 | }
16 |
17 | return (
18 |
19 |
20 |
21 |
27 | Import a new Schema
28 |
29 |
30 |
{schemaName}
31 |
32 |
33 |
34 |
35 |
36 |
37 | )
38 | };
39 |
40 | export default InputGqlSchema;
41 |
--------------------------------------------------------------------------------
/src/components/SchemaDownload/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useCallback } from 'react';
2 | import { useSpring, animated } from 'react-spring';
3 | import { MdClose } from 'react-icons/md';
4 | import '../../styles/Modal.css';
5 | import FileDownloader from "./FileDownloader";
6 | import SchemaUrlInput from './SchemaUrlInput';
7 |
8 | const Modal = ({ showModal, setShowModal}) => {
9 | const modalRef = useRef();
10 |
11 | //To bring the Modal from the top, cascade style
12 | const animation = useSpring({
13 | config: {
14 | duration: 250
15 | },
16 | opacity: showModal ? 1 : 0,
17 | transform: showModal ? `translateY(0%)` : `translateY(-100%)`
18 | });
19 |
20 | // Close the Modal with the X on the top right corner
21 | const closeModal = e => {
22 | if (modalRef.current === e.target) {
23 | setShowModal(false);
24 | }
25 | };
26 |
27 | // Close the modal with 'Esc' key
28 | const keyPress = useCallback(
29 | e => {
30 | if (e.key === 'Escape' && showModal) {
31 | setShowModal(false);
32 | }}, [setShowModal, showModal]);
33 |
34 | useEffect(() => {
35 | document.addEventListener('keydown', keyPress);
36 | return () => document.removeEventListener('keydown', keyPress);
37 | },[keyPress]);
38 |
39 | return (
40 | <>
41 | {showModal && (
42 |
43 |
44 |
45 |
46 |
Explore different Databases
47 |
48 |
49 | Bring any url to visualize your graphQl database on our system.
50 | Make sure to download your schema to
51 | the same directory your app is installed
52 | (aka. your PEACH folder)
53 |
54 |
55 |
Please confirm you want to overwrite the existing file.
56 |
57 |
setShowModal(prev => !prev)}>Close
58 |
59 |
60 |
setShowModal(prev => !prev)}
63 | />
64 |
65 |
66 |
67 | )}
68 | >
69 | )
70 | };
71 |
72 | export default Modal;
73 |
--------------------------------------------------------------------------------
/src/components/SchemaDownload/SchemaUrlInput.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import gqlEndpoint from '../../relay/gqlEndpoint';
3 | import db from '../../database/db';
4 |
5 | const execSync = require('child_process').execSync;
6 |
7 | const SchemaUrlInput = () => {
8 | const [schemaName, setSchemaName] = useState('give it a name!');
9 | const [schemaURL, setschemaURL] = useState('write schema URL here');
10 |
11 | const handleNameChange = (e) => {
12 | setSchemaName(e.target.value);
13 | }
14 |
15 | const handleURLChange = (e) => {
16 | setschemaURL(e.target.value);
17 | }
18 |
19 | const addSchema = (schemaName, schemaURL) => {
20 | gqlEndpoint.setUrl(schemaURL);
21 | execSync(`get-graphql-schema ${schemaURL} > schema.graphql`, { encoding: 'utf-8' });
22 | db.addURL(schemaName);
23 | }
24 |
25 | return (
26 |
27 | addSchema(schemaName, schemaURL)}>Add Schema Endpoint
28 |
29 | )
30 | };
31 |
32 | export default SchemaUrlInput;
--------------------------------------------------------------------------------
/src/components/StoreDisplay/StoreDisplay.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import RelayEnvironment from '../../relay/RelayEnvironment';
3 | import Button from 'react-bootstrap/Button';
4 | const store = RelayEnvironment.getStore();
5 |
6 | const StoreDisplay = ({queryToLoad, variables}) => {
7 |
8 | const [storeDisplay, setStoreDisplay] = useState(store.getSource());
9 |
10 | // update storeDisplay every time queryToLoad or variables change
11 | useEffect(() => {
12 | setStoreDisplay(store.getSource());
13 | }, [queryToLoad, variables])
14 |
15 | return (
16 |
17 |
18 | {JSON.stringify(storeDisplay, null, 2)}
19 |
20 |
21 | store.snapshot()} >
26 | Save Snapshot
27 |
28 | {
33 | store.restore();
34 | setStoreDisplay(store.getSource());
35 | }}>
36 | Restore Last Snapshot
37 |
38 |
39 |
40 |
41 | )
42 | };
43 |
44 | export default StoreDisplay;
--------------------------------------------------------------------------------
/src/components/VariableInput/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../../styles/styles.css';
3 |
4 | //importing library for code editor
5 | import 'codemirror/lib/codemirror.css';
6 | import 'codemirror/mode/javascript/javascript';
7 | import { Controlled as ControlledEditor } from 'react-codemirror2';
8 |
9 | const VariableInput = ({variables, setVariables}) => {
10 |
11 | const options = {
12 | lineWrapping: true,
13 | lint: true,
14 | mode: {
15 | name: 'javascript',
16 | json: true
17 | },
18 | lineNumbers: true,
19 | theme: 'default height4rem readonly',
20 | };
21 |
22 | const handleChange = (editor, data, value) => {
23 | setVariables(value);
24 | }
25 |
26 | return (
27 |
28 |
Variable Input
29 |
35 |
36 | );
37 | };
38 |
39 | export default VariableInput
--------------------------------------------------------------------------------
/src/components/hooks/http-common.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export default axios.create({
4 | baseURL: "http://localhost:8080", // '/'
5 | headers: {
6 | "Content-type": "application/json"
7 | }
8 | });
--------------------------------------------------------------------------------
/src/components/hooks/upload-files.service.js:
--------------------------------------------------------------------------------
1 | import http from "./http-common";
2 |
3 | class UploadFilesService {
4 | upload(file, onUploadProgress) {
5 | let formData = new FormData();
6 |
7 | formData.append("file", file);
8 |
9 | return http.post("/upload", formData, {
10 | headers: {
11 | "Content-Type": "multipart/form-data",
12 | },
13 | onUploadProgress,
14 | });
15 | }
16 |
17 | getFiles() {
18 | return http.get("/files");
19 | }
20 | }
21 |
22 | export default new UploadFilesService();
--------------------------------------------------------------------------------
/src/components/hooks/uploaderHook.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from 'react-bootstrap/Button';
3 | const electron = window.require('electron');
4 | const {shell} = window.require('electron');
5 | const remote = electron.remote;
6 | const {dialog} = remote;
7 | import * as fs from 'fs';
8 |
9 | const uploaderHook = () => {
10 |
11 | function readFile(filepath) {
12 | fs.readFileSync(filepath, 'utf-8', function (err, data) {
13 | if(err){
14 | alert("An error ocurred reading the file :" + err.message);
15 | return;
16 | }
17 |
18 | document.getElementById("content-editor").value = data;
19 | });
20 | }
21 |
22 | function deleteFile(filepath){
23 | fs.exists(filepath, function(exists) {
24 | if(exists) {
25 | // File exists deletings
26 | fs.unlink(filepath,function(err){
27 | if(err){
28 | alert("An error ocurred updating the file"+ err.message);
29 | console.log(err);
30 | return;
31 | }
32 | });
33 | } else {
34 | alert("This file doesn't exist, cannot delete");
35 | }
36 | });
37 | }
38 |
39 | function saveChanges(filepath,content){
40 | fs.writeFileSync(filepath, content, function (err) {
41 | if(err){
42 | alert("An error ocurred updating the file"+ err.message);
43 | console.log(err);
44 | return;
45 | }
46 |
47 | alert("The file has been succesfully saved");
48 | });
49 | }
50 |
51 | };
52 |
53 |
54 | export default uploaderHook;
--------------------------------------------------------------------------------
/src/components/hooks/useFileDownloader.js:
--------------------------------------------------------------------------------
1 | import Downloader from "../Downloader/index";
2 | import React, { useState } from "react";
3 | import { v4 as uuid } from "uuid";
4 |
5 | const useFileDownloader = () => {
6 | const [files, setFiles] = useState(() => []);
7 |
8 | // Sets up the file to be downloaded
9 | const download = (file) =>
10 | setFiles((fileList) => [...fileList, { ...file, downloadId: uuid() }]);
11 |
12 | // with the unique id, removes the file from the list (blurb)
13 | const remove = (removeId) =>
14 | setFiles((files) => [
15 | ...files.filter((file) => file.downloadId !== removeId),
16 | ]);
17 |
18 | return [
19 | (e) => download(e),
20 | files.length > 0 ? (
21 | remove(e)} />
22 | ) : null,
23 | ];
24 | };
25 |
26 | export default useFileDownloader;
27 |
--------------------------------------------------------------------------------
/src/components/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/peach/e5c65cd4cc287844e6ab224d16c0484995eb7d34/src/components/icon.png
--------------------------------------------------------------------------------
/src/database/db.js:
--------------------------------------------------------------------------------
1 | /*
2 | this file contains middleware functions for reading, writing,
3 | updating and deleting to local history. the whole module is
4 | exported as a single object and handles both query history
5 | and previous saved versions of the imported.js file
6 | */
7 |
8 | const fs = require('fs');
9 | const path = require('path');
10 | import gqlEndpoint from '../relay/gqlEndpoint';
11 |
12 | const queryHistoryJSON = require('./queryHistory.json');
13 | const queryHistoryPath = path.resolve('./src/database/queryHistory.json');
14 | let queryHistoryArray = queryHistoryJSON || [];
15 |
16 | const importedHistoryJSON = require('./importedHistory.json');
17 | const importedHistoryPath = path.resolve('./src/database/importedHistory.json');
18 | let importedHistoryArray = importedHistoryJSON || [];
19 |
20 | const schemaHistoryJSON = require('./schemaHistory.json');
21 | const schemaHistoryPath = path.resolve('./src/database/schemaHistory.json');
22 | const schemaHistoryArray = schemaHistoryJSON || [];
23 |
24 | const db = {};
25 |
26 | // sets a history item on a local json database equal to the relevant array
27 | db.write = (array, path) => {
28 | fs.writeFile(path, JSON.stringify(array, null, 2), (err, data) => {
29 | if (err) console.error(err);
30 | });
31 | };
32 |
33 | /* function resets the passed-in history array
34 | to equal the value of imported json history contents
35 | or, lacking that, an empty array */
36 | db.reset = (array, json) => {
37 | array = json || [];
38 | };
39 |
40 | /* add a new query object to the queryHistory.json db and local
41 | queryHistoryArray. Each object holds the text of the graphQL query,
42 | a time and date stamp and the graphQL endpoint URL. If the function
43 | finds that the local queryHistoryArray already has an object with that
44 | same queryText, it will remove that element from the array and treat the
45 | newly added element as its replacement. */
46 | db.addQuery = (queryText) => {
47 | const newEntry = {};
48 | newEntry.queryText = queryText;
49 | // remove duplicates that match the query text exactly
50 | const index = queryHistoryArray.findIndex(entry => entry.queryText === newEntry.queryText);
51 | if (index !== -1) {
52 | queryHistoryArray.splice(index, 1);
53 | }
54 | newEntry.url = gqlEndpoint.url;
55 | newEntry.timeStamp = new Date().toLocaleTimeString();
56 | newEntry.dateStamp = new Date().toLocaleDateString();
57 | queryHistoryArray.unshift(newEntry);
58 | db.sync(queryHistoryArray, queryHistoryJSON, queryHistoryPath);
59 | }
60 |
61 | /* the same as addQuery, but this time for the imported.js query files,
62 | the function should ideally be called every time a new imported.js
63 | file is uploaded or changed so that we can keep a history of previous
64 | imported files. the importedHistory.json objects contain the full file
65 | contents, a user-created name, a time and date stamp and the GQL endpoint */
66 | db.addImported = (fileContents, name) => {
67 | const newEntry = {};
68 | newEntry.fileContents = fileContents;
69 | newEntry.name = name;
70 | newEntry.url = gqlEndpoint.url;
71 | const index = queryHistoryArray.findIndex(entry => entry.name === newEntry.name);
72 | if (index !== -1) {
73 | importedHistoryArray.splice(index, 1);
74 | }
75 | newEntry.timeStamp = new Date().toLocaleTimeString();
76 | newEntry.dateStamp = new Date().toLocaleDateString();
77 | importedHistoryArray.unshift(newEntry);
78 | db.sync(importedHistoryArray, importedHistoryJSON, importedHistoryPath);
79 | }
80 |
81 | /* adds a url to the schemaHistory database, which is an object,
82 | unlike the other json files */
83 | db.addURL = (schemaName) => {
84 | const newEntry = {};
85 | newEntry.name = schemaName;
86 | newEntry.url = gqlEndpoint.url;
87 | const index = schemaHistoryArray.findIndex(entry => entry.url === newEntry.url);
88 | if (index !== -1) {
89 | schemaHistoryArray.splice(index, 1);
90 | }
91 | newEntry.lastUsed = new Date().toLocaleString();
92 | schemaHistoryArray.unshift(newEntry);
93 | db.sync(schemaHistoryArray, schemaHistoryJSON, schemaHistoryPath)
94 | }
95 |
96 | /* writes the passed-in history array to the relevant json file and resets
97 | the local history array to keep it up to date */
98 | db.sync = (array, json, path) => {
99 | db.write(array, path);
100 | db.reset(array, json);
101 | };
102 |
103 | // clears the passed-in history permanently
104 | db.clear = (path) => {
105 | fs.writeFileSync(path, '');
106 | }
107 |
108 | // these functions return the relevant history array or object
109 | db.getQueryHistory = () => queryHistoryArray;
110 | db.getImportedHistory = () => importedHistoryArray;
111 | db.getSchemaHistory = () => schemaHistoryArray;
112 |
113 | export default db;
--------------------------------------------------------------------------------
/src/database/importedHistory.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/src/database/queryHistory.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "queryText": "query writtenQuery(\n $id: Int\n) {\n Media(id: $id, type: ANIME) {\n _id: id\n title {\n native\n \n }\n }\n}\n",
4 | "url": "https://graphql.anilist.co",
5 | "timeStamp": "2:03:24 PM",
6 | "dateStamp": "8/18/2021"
7 | },
8 | {
9 | "queryText": "query importedCharacterQuery {\n Character(id: 80) {\n name {\n full\n native\n }\n }\n}\n",
10 | "url": "https://ghibliql.herokuapp.com",
11 | "timeStamp": "7:32:41 PM",
12 | "dateStamp": "6/15/2021"
13 | },
14 | {
15 | "queryText": "query importedLongMediaQuery(\n $id: Int\n) {\n Media(id: $id, type: ANIME) {\n _id: id\n title {\n native\n english\n }\n }\n}\n",
16 | "url": "https://graphql.anilist.co",
17 | "timeStamp": "7:32:20 PM",
18 | "dateStamp": "6/15/2021"
19 | },
20 | {
21 | "queryText": "query importedMediaQuery {\n Media(id: 80) {\n title {\n english\n romaji\n }\n }\n}\n",
22 | "url": "https://graphql.anilist.co",
23 | "timeStamp": "7:32:20 PM",
24 | "dateStamp": "6/15/2021"
25 | },
26 | {
27 | "queryText": "query importedThreadQuery {\n Thread(id: 80) {\n title\n }\n}\n",
28 | "url": "https://graphql.anilist.co",
29 | "timeStamp": "7:32:20 PM",
30 | "dateStamp": "6/15/2021"
31 | },
32 | {
33 | "queryText": "query importedUserQuery {\n User(id: 80) {\n name\n }\n}\n",
34 | "url": "https://graphql.anilist.co",
35 | "timeStamp": "7:32:20 PM",
36 | "dateStamp": "6/15/2021"
37 | },
38 | {
39 | "queryText": "query writtenQuery(\n $id: Int\n) {\n Media(id: $id, type: ANIME) {\n _id: id\n title {\n native\n english\n romaji\n }\n }\n}\n",
40 | "url": "https://graphql.anilist.co",
41 | "timeStamp": "7:32:20 PM",
42 | "dateStamp": "6/15/2021"
43 | },
44 | {
45 | "queryText": "query writtenQuery(\n $id: Int\n) {\n Media(id: $id, type: ANIME) {\n _id: id\n title {\n native\n romaji\n english\n }\n }\n}\n",
46 | "timeStamp": "2:24:59 PM",
47 | "dateStamp": "6/14/2021"
48 | }
49 | ]
--------------------------------------------------------------------------------
/src/database/schemaHistory.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "anime list",
4 | "url": "https://graphql.anilist.co",
5 | "lastUsed": "8/18/2021, 2:00:13 PM"
6 | },
7 | {
8 | "name": "dnd5e",
9 | "url": "https://www.dnd5eapi.co/graphql",
10 | "lastUsed": "8/18/2021, 2:00:02 PM"
11 | },
12 | {
13 | "name": "StudioGhibli",
14 | "url": "https://ghibliql.herokuapp.com",
15 | "lastUsed": "6/15/2021, 7:32:27 PM"
16 | }
17 | ]
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | entry point for the application and wraps the root component in RelayEnvironmentProvider
3 | */
4 |
5 | import React, { Suspense } from 'react';
6 | import { render } from 'react-dom';
7 | import App from './App';
8 | import { RelayEnvironmentProvider } from 'react-relay';
9 | import RelayEnvironment from './relay/RelayEnvironment';
10 | import Logo from './components/Logo'
11 |
12 | // * Just to speed up the process of decorating the App
13 | import 'bootstrap/dist/css/bootstrap.min.css'
14 |
15 | // ? In case we decide to operate with our own css
16 | import './styles/App.css';
17 |
18 |
19 | /* Since we are using HtmlWebpackPlugin WITHOUT a template,
20 | we need to create our own root node in the body element before
21 | rendering into it */
22 | let root = document.createElement('div');
23 | root.id = 'root';
24 | document.body.appendChild(root);
25 |
26 | render(
27 |
28 | }>
29 |
30 |
31 | ,
32 | document.getElementById('root'));
--------------------------------------------------------------------------------
/src/relay/RelayEnvironment.js:
--------------------------------------------------------------------------------
1 | /*
2 | sets up the relay environment, tying it to our fetchGraphQL function to reach the endpoint,
3 | generating a new Network and Store for caching and logging our queries to the console
4 | */
5 |
6 | import { Environment, Network, RecordSource, Store } from 'relay-runtime';
7 | import fetchGraphQL from './fetchGraphQL';
8 |
9 | async function fetchRelay(params, variables) {
10 | console.log(`fetching query ${params.name} with ${JSON.stringify(variables)}`);
11 | return fetchGraphQL(params.text, variables);
12 | }
13 |
14 | // export a singleton instance of relay environment configured with our network function
15 |
16 | export default new Environment({
17 | network: Network.create(fetchRelay),
18 | store: new Store(new RecordSource()),
19 | });
--------------------------------------------------------------------------------
/src/relay/__generated__/importedLongMediaQuery.graphql.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @flow
3 | */
4 |
5 | /* eslint-disable */
6 |
7 | 'use strict';
8 |
9 | /*::
10 | import type { ConcreteRequest } from 'relay-runtime';
11 | export type importedLongMediaQueryVariables = {|
12 | id?: ?number
13 | |};
14 | export type importedLongMediaQueryResponse = {|
15 | +Media: ?{|
16 | +_id: number,
17 | +title: ?{|
18 | +native: ?string,
19 | +english: ?string,
20 | |},
21 | |}
22 | |};
23 | export type importedLongMediaQuery = {|
24 | variables: importedLongMediaQueryVariables,
25 | response: importedLongMediaQueryResponse,
26 | |};
27 | */
28 |
29 |
30 | /*
31 | query importedLongMediaQuery(
32 | $id: Int
33 | ) {
34 | Media(id: $id, type: ANIME) {
35 | _id: id
36 | title {
37 | native
38 | english
39 | }
40 | }
41 | }
42 | */
43 |
44 | const node/*: ConcreteRequest*/ = (function(){
45 | var v0 = [
46 | {
47 | "defaultValue": null,
48 | "kind": "LocalArgument",
49 | "name": "id"
50 | }
51 | ],
52 | v1 = [
53 | {
54 | "alias": null,
55 | "args": [
56 | {
57 | "kind": "Variable",
58 | "name": "id",
59 | "variableName": "id"
60 | },
61 | {
62 | "kind": "Literal",
63 | "name": "type",
64 | "value": "ANIME"
65 | }
66 | ],
67 | "concreteType": "Media",
68 | "kind": "LinkedField",
69 | "name": "Media",
70 | "plural": false,
71 | "selections": [
72 | {
73 | "alias": "_id",
74 | "args": null,
75 | "kind": "ScalarField",
76 | "name": "id",
77 | "storageKey": null
78 | },
79 | {
80 | "alias": null,
81 | "args": null,
82 | "concreteType": "MediaTitle",
83 | "kind": "LinkedField",
84 | "name": "title",
85 | "plural": false,
86 | "selections": [
87 | {
88 | "alias": null,
89 | "args": null,
90 | "kind": "ScalarField",
91 | "name": "native",
92 | "storageKey": null
93 | },
94 | {
95 | "alias": null,
96 | "args": null,
97 | "kind": "ScalarField",
98 | "name": "english",
99 | "storageKey": null
100 | }
101 | ],
102 | "storageKey": null
103 | }
104 | ],
105 | "storageKey": null
106 | }
107 | ];
108 | return {
109 | "fragment": {
110 | "argumentDefinitions": (v0/*: any*/),
111 | "kind": "Fragment",
112 | "metadata": null,
113 | "name": "importedLongMediaQuery",
114 | "selections": (v1/*: any*/),
115 | "type": "Query",
116 | "abstractKey": null
117 | },
118 | "kind": "Request",
119 | "operation": {
120 | "argumentDefinitions": (v0/*: any*/),
121 | "kind": "Operation",
122 | "name": "importedLongMediaQuery",
123 | "selections": (v1/*: any*/)
124 | },
125 | "params": {
126 | "cacheID": "4a8b4c23ce8e82c15862b842b927767e",
127 | "id": null,
128 | "metadata": {},
129 | "name": "importedLongMediaQuery",
130 | "operationKind": "query",
131 | "text": "query importedLongMediaQuery(\n $id: Int\n) {\n Media(id: $id, type: ANIME) {\n _id: id\n title {\n native\n english\n }\n }\n}\n"
132 | }
133 | };
134 | })();
135 | // prettier-ignore
136 | (node/*: any*/).hash = 'e689d513285ffcd78498d3756cf3e3a4';
137 |
138 | module.exports = node;
139 |
--------------------------------------------------------------------------------
/src/relay/__generated__/importedMediaQuery.graphql.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @flow
3 | */
4 |
5 | /* eslint-disable */
6 |
7 | 'use strict';
8 |
9 | /*::
10 | import type { ConcreteRequest } from 'relay-runtime';
11 | export type importedMediaQueryVariables = {||};
12 | export type importedMediaQueryResponse = {|
13 | +Media: ?{|
14 | +title: ?{|
15 | +english: ?string
16 | |}
17 | |}
18 | |};
19 | export type importedMediaQuery = {|
20 | variables: importedMediaQueryVariables,
21 | response: importedMediaQueryResponse,
22 | |};
23 | */
24 |
25 |
26 | /*
27 | query importedMediaQuery {
28 | Media(id: 80) {
29 | title {
30 | english
31 | }
32 | }
33 | }
34 | */
35 |
36 | const node/*: ConcreteRequest*/ = (function(){
37 | var v0 = [
38 | {
39 | "alias": null,
40 | "args": [
41 | {
42 | "kind": "Literal",
43 | "name": "id",
44 | "value": 80
45 | }
46 | ],
47 | "concreteType": "Media",
48 | "kind": "LinkedField",
49 | "name": "Media",
50 | "plural": false,
51 | "selections": [
52 | {
53 | "alias": null,
54 | "args": null,
55 | "concreteType": "MediaTitle",
56 | "kind": "LinkedField",
57 | "name": "title",
58 | "plural": false,
59 | "selections": [
60 | {
61 | "alias": null,
62 | "args": null,
63 | "kind": "ScalarField",
64 | "name": "english",
65 | "storageKey": null
66 | }
67 | ],
68 | "storageKey": null
69 | }
70 | ],
71 | "storageKey": "Media(id:80)"
72 | }
73 | ];
74 | return {
75 | "fragment": {
76 | "argumentDefinitions": [],
77 | "kind": "Fragment",
78 | "metadata": null,
79 | "name": "importedMediaQuery",
80 | "selections": (v0/*: any*/),
81 | "type": "Query",
82 | "abstractKey": null
83 | },
84 | "kind": "Request",
85 | "operation": {
86 | "argumentDefinitions": [],
87 | "kind": "Operation",
88 | "name": "importedMediaQuery",
89 | "selections": (v0/*: any*/)
90 | },
91 | "params": {
92 | "cacheID": "6614549f14a36b9789a0079ac7e87d20",
93 | "id": null,
94 | "metadata": {},
95 | "name": "importedMediaQuery",
96 | "operationKind": "query",
97 | "text": "query importedMediaQuery {\n Media(id: 80) {\n title {\n english\n }\n }\n}\n"
98 | }
99 | };
100 | })();
101 | // prettier-ignore
102 | (node/*: any*/).hash = '843bb8c6b1e72fc01d2e70e90b2d0e35';
103 |
104 | module.exports = node;
105 |
--------------------------------------------------------------------------------
/src/relay/__generated__/importedThreadQuery.graphql.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @flow
3 | */
4 |
5 | /* eslint-disable */
6 |
7 | 'use strict';
8 |
9 | /*::
10 | import type { ConcreteRequest } from 'relay-runtime';
11 | export type importedThreadQueryVariables = {||};
12 | export type importedThreadQueryResponse = {|
13 | +Thread: ?{|
14 | +title: ?string
15 | |}
16 | |};
17 | export type importedThreadQuery = {|
18 | variables: importedThreadQueryVariables,
19 | response: importedThreadQueryResponse,
20 | |};
21 | */
22 |
23 |
24 | /*
25 | query importedThreadQuery {
26 | Thread(id: 80) {
27 | title
28 | }
29 | }
30 | */
31 |
32 | const node/*: ConcreteRequest*/ = (function(){
33 | var v0 = [
34 | {
35 | "alias": null,
36 | "args": [
37 | {
38 | "kind": "Literal",
39 | "name": "id",
40 | "value": 80
41 | }
42 | ],
43 | "concreteType": "Thread",
44 | "kind": "LinkedField",
45 | "name": "Thread",
46 | "plural": false,
47 | "selections": [
48 | {
49 | "alias": null,
50 | "args": null,
51 | "kind": "ScalarField",
52 | "name": "title",
53 | "storageKey": null
54 | }
55 | ],
56 | "storageKey": "Thread(id:80)"
57 | }
58 | ];
59 | return {
60 | "fragment": {
61 | "argumentDefinitions": [],
62 | "kind": "Fragment",
63 | "metadata": null,
64 | "name": "importedThreadQuery",
65 | "selections": (v0/*: any*/),
66 | "type": "Query",
67 | "abstractKey": null
68 | },
69 | "kind": "Request",
70 | "operation": {
71 | "argumentDefinitions": [],
72 | "kind": "Operation",
73 | "name": "importedThreadQuery",
74 | "selections": (v0/*: any*/)
75 | },
76 | "params": {
77 | "cacheID": "9261e373240fa4960d3982041fe4eceb",
78 | "id": null,
79 | "metadata": {},
80 | "name": "importedThreadQuery",
81 | "operationKind": "query",
82 | "text": "query importedThreadQuery {\n Thread(id: 80) {\n title\n }\n}\n"
83 | }
84 | };
85 | })();
86 | // prettier-ignore
87 | (node/*: any*/).hash = '746f15e7074c14e67bddd74a1b26be8b';
88 |
89 | module.exports = node;
90 |
--------------------------------------------------------------------------------
/src/relay/__generated__/writtenQuery.graphql.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @flow
3 | */
4 |
5 | /* eslint-disable */
6 |
7 | 'use strict';
8 |
9 | /*::
10 | import type { ConcreteRequest } from 'relay-runtime';
11 | export type writtenQueryVariables = {|
12 | id?: ?number
13 | |};
14 | export type writtenQueryResponse = {|
15 | +Media: ?{|
16 | +_id: number,
17 | +title: ?{|
18 | +native: ?string,
19 | +english: ?string,
20 | +romaji: ?string,
21 | |},
22 | |}
23 | |};
24 | export type writtenQuery = {|
25 | variables: writtenQueryVariables,
26 | response: writtenQueryResponse,
27 | |};
28 | */
29 |
30 |
31 | /*
32 | query writtenQuery(
33 | $id: Int
34 | ) {
35 | Media(id: $id, type: ANIME) {
36 | _id: id
37 | title {
38 | native
39 | english
40 | romaji
41 | }
42 | }
43 | }
44 | */
45 |
46 | const node/*: ConcreteRequest*/ = (function(){
47 | var v0 = [
48 | {
49 | "defaultValue": null,
50 | "kind": "LocalArgument",
51 | "name": "id"
52 | }
53 | ],
54 | v1 = [
55 | {
56 | "alias": null,
57 | "args": [
58 | {
59 | "kind": "Variable",
60 | "name": "id",
61 | "variableName": "id"
62 | },
63 | {
64 | "kind": "Literal",
65 | "name": "type",
66 | "value": "ANIME"
67 | }
68 | ],
69 | "concreteType": "Media",
70 | "kind": "LinkedField",
71 | "name": "Media",
72 | "plural": false,
73 | "selections": [
74 | {
75 | "alias": "_id",
76 | "args": null,
77 | "kind": "ScalarField",
78 | "name": "id",
79 | "storageKey": null
80 | },
81 | {
82 | "alias": null,
83 | "args": null,
84 | "concreteType": "MediaTitle",
85 | "kind": "LinkedField",
86 | "name": "title",
87 | "plural": false,
88 | "selections": [
89 | {
90 | "alias": null,
91 | "args": null,
92 | "kind": "ScalarField",
93 | "name": "native",
94 | "storageKey": null
95 | },
96 | {
97 | "alias": null,
98 | "args": null,
99 | "kind": "ScalarField",
100 | "name": "english",
101 | "storageKey": null
102 | },
103 | {
104 | "alias": null,
105 | "args": null,
106 | "kind": "ScalarField",
107 | "name": "romaji",
108 | "storageKey": null
109 | }
110 | ],
111 | "storageKey": null
112 | }
113 | ],
114 | "storageKey": null
115 | }
116 | ];
117 | return {
118 | "fragment": {
119 | "argumentDefinitions": (v0/*: any*/),
120 | "kind": "Fragment",
121 | "metadata": null,
122 | "name": "writtenQuery",
123 | "selections": (v1/*: any*/),
124 | "type": "Query",
125 | "abstractKey": null
126 | },
127 | "kind": "Request",
128 | "operation": {
129 | "argumentDefinitions": (v0/*: any*/),
130 | "kind": "Operation",
131 | "name": "writtenQuery",
132 | "selections": (v1/*: any*/)
133 | },
134 | "params": {
135 | "cacheID": "724a7d3a8936f1555e419c87131b2696",
136 | "id": null,
137 | "metadata": {},
138 | "name": "writtenQuery",
139 | "operationKind": "query",
140 | "text": "query writtenQuery(\n $id: Int\n) {\n Media(id: $id, type: ANIME) {\n _id: id\n title {\n native\n english\n romaji\n }\n }\n}\n"
141 | }
142 | };
143 | })();
144 | // prettier-ignore
145 | (node/*: any*/).hash = 'acd556a0d1d06081425ac104a5f4bec3';
146 |
147 | module.exports = node;
148 |
--------------------------------------------------------------------------------
/src/relay/aliasID.js:
--------------------------------------------------------------------------------
1 | /* module handles aliasing any id fields in a graphql query string as _id */
2 |
3 | export default function aliasID(queryString) {
4 | const idRegex = /(? {
11 | if (typeof newUrl === 'string') {
12 | gqlEndpoint.url = newUrl;
13 | }
14 | if (auth) {
15 | gqlEndpoint.requiresAuth = true;
16 | gqlEndpoint.auth = auth;
17 | }
18 | else gqlEndpoint.auth = false;
19 | };
20 | gqlEndpoint.requiresAuth = false;
21 | gqlEndpoint.auth = null;
22 |
23 | export default gqlEndpoint;
--------------------------------------------------------------------------------
/src/relay/imported.js:
--------------------------------------------------------------------------------
1 | import graphql from 'graphql';
2 |
3 | graphql`query importedMediaQuery {Media (id: 80){
4 | title {
5 | english
6 | }
7 | }}`;
8 |
9 | graphql`query importedLongMediaQuery($id: Int) {
10 | Media(id: $id, type: ANIME) {
11 | _id: id
12 | title {
13 | native
14 | english
15 | }
16 | }
17 | }`;
18 |
19 | graphql`query importedThreadQuery {Thread (id: 80){
20 | title
21 | }}`;
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/relay/makeJsonSchema.js:
--------------------------------------------------------------------------------
1 | /*
2 | handles the task of parsing schema.graphql into json,
3 | which allows other components to render and manipulate the data
4 | */
5 |
6 | const { parse, visit, print } = require('graphql/language');
7 | const path = require('path');
8 | const fs = require('fs');
9 |
10 | /*
11 | this function creates a JSON representation of the Schema and
12 | fields from the graphQL schema by parsing the abstract syntax tree
13 | */
14 | export default function makeJsonSchema() {
15 | const output = [];
16 | const pathToSchema = path.resolve('./schema.graphql');
17 | const schemaString = fs.readFileSync(pathToSchema, 'utf8', async (err, data) => {
18 | if (err) console.error(err);
19 | return data;
20 | });
21 | parse(schemaString).definitions.forEach(ast => {
22 | // currently ignoring the definitions that are not for Object Types
23 | // render therefore excludes Mutations, Subscriptions and Fragment definitions
24 | if (ast.kind === 'ObjectTypeDefinition') {
25 | const astObject = {};
26 | astObject.name = ast.name.value;
27 | astObject.fields = [];
28 | visit(ast, {
29 | FieldDefinition(node) {
30 | const text = print(node);
31 | astObject.fields.push(
32 | {
33 | // note that the comments are saved as 'note' and maintained for rendering later
34 | // if no notes, make sure this defaults to an empty string
35 | note: text.split("\"")[3] || '',
36 | // sometimes there are no quotes or only quotes around the text
37 | // in that case, we would want the full text, rather than an empty string
38 | type: text.split('\"')[6] || text,
39 | }
40 | );
41 | }
42 | });
43 | output.push(astObject);
44 | }
45 | });
46 | return output;
47 | }
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/relay/written.js:
--------------------------------------------------------------------------------
1 | import graphql from 'graphql';
2 | export default graphql`query writtenQuery($id: Int) {
3 | Media(id: $id, type: ANIME) {
4 | _id: id
5 | title {
6 | native
7 | english
8 | romaji
9 | }
10 | }
11 | }`;
12 |
--------------------------------------------------------------------------------
/src/styles/App.css:
--------------------------------------------------------------------------------
1 | .AppRelay {
2 | background-color: #ffeae17a;
3 | height: calc(100% - 60px);
4 | }
5 |
6 | .importedMode {
7 | background-color: #f7cfbc8f;
8 | height: calc(100% + 60px);
9 | height: 50.5rem;
10 | }
11 |
12 | .containerApp2 {
13 | display: flex;
14 | align-content: space-around;
15 | align-items: flex-start;
16 | }
17 |
18 | code {
19 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
20 | monospace;
21 | }
22 |
23 | ._banner {
24 | padding-top: 0.15rem;
25 | height: 3rem;
26 | text-align: center;
27 | display: flex;
28 | align-items: center;
29 | justify-content: space-around;
30 | background-color: hsla(11, 63%, 52%, 0.87);
31 | box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2);
32 | border-radius: 0 0 0.5rem 0.5rem;
33 | }
34 |
35 | ._banner a {
36 | text-decoration: none;
37 | }
38 |
39 | ._footer {
40 | padding-top: 0.15rem;
41 | height: 3rem;
42 | text-align: center;
43 | display: flex;
44 | align-items: center;
45 | justify-content: space-around;
46 | background-color: hsla(11, 63%, 52%, 0.87);
47 | box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2);
48 | border-radius: 0.5rem 0.5rem 0 0 ;
49 | }
50 |
51 | ._footer a {
52 | text-decoration: none;
53 | }
54 |
55 | .logo {
56 | height: 40rem;
57 | display: flex;
58 | align-items: center;
59 | justify-content: center
60 | }
61 |
62 | ._schemaDisplay {
63 | height: 26.8rem;
64 | background-color: #ececec;
65 | box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2);
66 | }
67 |
68 | .schema-display-container {
69 | /* background-color: #fbb78d; */
70 | height: 10rem;
71 | display: flex;
72 | align-items: baseline;
73 | overflow-x: hidden;
74 | }
75 |
76 | ._inputButton {
77 | /* align: center; */
78 | display: flex;
79 | align-items: baseline;
80 | }
81 |
82 | ._input {
83 | /* background-color: #fbb78d; */
84 | border-radius: 1rem;
85 | }
86 |
87 | ._inputSchemaUrl {
88 | width: 100%;
89 | text-align: center;
90 | padding: 0.3rem;
91 | font-size: 1.2em;
92 | }
93 |
94 | ._inline {
95 | width: 100%;
96 | /* display: flex; */
97 | flex-flow: row wrap;
98 | align-items: center;
99 | justify-content: space-around;
100 | }
101 |
102 | ._downloadedSchema {
103 | font-size: 0.8em;
104 | /* color: #ec4a19 */
105 | }
106 |
107 | ._UrlInput {
108 | display: inline-block;
109 | height: 1.8rem;
110 | border: solid 1px black;
111 | border-radius: 0.2rem;
112 | }
113 |
114 |
115 | ._variableInput {
116 | padding: 0.2rem;
117 | text-align: center;
118 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
119 | }
120 |
121 | ._variableInputInner {
122 | /* padding: 0.2rem; */
123 | text-align: left;
124 | /* background-color: #962d10; */
125 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
126 | }
127 |
128 | ._queryContainer {
129 | /* background-color: #f38b58; */
130 | height: 100%;
131 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
132 | }
133 |
134 | ._response {
135 | /* background-color: #eb8060; */
136 | height: 100%;
137 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
138 | }
139 |
140 | ._response2 {
141 | /* background-color: #eb8060; */
142 | height: 91vh;
143 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
144 | }
145 |
146 | /* SCHEMA DISPLAY */
147 |
148 | ._schemaDisplay {
149 | height: 100%;
150 | }
151 |
152 | #schema-display-container > ul {
153 | margin: 0;
154 | padding: 0;
155 | overflow: scroll;
156 | height: 41vh;
157 | }
158 |
159 | li.schema-display {
160 | font-size: .875rem;
161 | list-style: none;
162 | margin-bottom: .5rem;
163 | }
164 |
165 | p.schemaName {
166 | margin: 0;
167 | padding: 0;
168 | }
169 |
170 | p.schema {
171 | font-size: .75rem;
172 | margin: 0 0 0 .875rem;
173 | padding: 0;
174 | }
175 |
176 | ._editorDisplay {
177 | align-content: center;
178 | text-align: center;
179 | /* height: 50%; */
180 | }
181 |
182 | ._storeDisplay {
183 | font-family: Verdana;
184 | font-size: 0.8rem;
185 | }
186 |
187 | ._storeDisplay h6 {
188 | align-content: center;
189 | text-align: center;
190 | }
191 |
192 | .schemaURLInput {
193 | font-size: 1rem;
194 | }
195 |
196 | ._newQuerySelector {
197 | align-content: center;
198 | text-align: center;
199 | max-height: 40vh;
200 | overflow-y: scroll;
201 | overflow-x: hidden;
202 | }
203 |
204 | ._queryButton {
205 | font-size: 0.5rem;
206 | }
207 |
208 |
209 | body {
210 | margin: 0;
211 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
212 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
213 | sans-serif;
214 | -webkit-font-smoothing: antialiased;
215 | -moz-osx-font-smoothing: grayscale;
216 | background-color: #fbf9f8;
217 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 1200 800'%3E%3Cdefs%3E%3CradialGradient id='a' cx='0' cy='800' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcede5'/%3E%3Cstop offset='1' stop-color='%23fcede5' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='b' cx='1200' cy='800' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23feeadb'/%3E%3Cstop offset='1' stop-color='%23feeadb' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='c' cx='600' cy='0' r='600' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcdec7'/%3E%3Cstop offset='1' stop-color='%23fcdec7' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='d' cx='600' cy='800' r='600' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fbf9f8'/%3E%3Cstop offset='1' stop-color='%23fbf9f8' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='e' cx='0' cy='0' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fbe1d2'/%3E%3Cstop offset='1' stop-color='%23fbe1d2' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='f' cx='1200' cy='0' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fbdcbc'/%3E%3Cstop offset='1' stop-color='%23fbdcbc' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect fill='url(%23a)' width='1200' height='800'/%3E%3Crect fill='url(%23b)' width='1200' height='800'/%3E%3Crect fill='url(%23c)' width='1200' height='800'/%3E%3Crect fill='url(%23d)' width='1200' height='800'/%3E%3Crect fill='url(%23e)' width='1200' height='800'/%3E%3Crect fill='url(%23f)' width='1200' height='800'/%3E%3C/svg%3E");
218 | background-attachment: fixed;
219 | background-size: cover;
220 | }
221 |
--------------------------------------------------------------------------------
/src/styles/Modal.css:
--------------------------------------------------------------------------------
1 | .Background {
2 | z-index: 1000;
3 | width: 65%;
4 | height: 80%;
5 | position: fixed;
6 | /* margin-left: 10rem; */
7 | margin-top: -50rem;
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | background-color: rgba(235, 129, 96, 0.822);
12 | border-radius: 0.5rem;
13 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
14 | }
15 |
16 | .ModalWrapper {
17 | width: 50rem;
18 | height: 30rem;
19 | box-shadow: 0 5px 16px rgba(0, 0, 0, 0.2);
20 | color: #000;
21 | display: grid;
22 | grid-template-columns: 1fr;
23 | position: relative;
24 | z-index: 10;
25 | border-radius: 11px;
26 | }
27 |
28 | .ModalContent {
29 | /* height: 100%; */
30 | padding: 2rem;
31 | background: #fff;
32 | display: flex;
33 | flex-direction: column;
34 | justify-content: center;
35 | align-items: center;
36 | line-height: 1.8;
37 | color: #141414;
38 | border-radius: 0.5rem;
39 | }
40 |
41 | .ModalContent p {
42 | margin-bottom: 1rem;
43 | font-size: 0.9rem;
44 | }
45 |
46 | .ModalContent button {
47 | /* padding: 10px 10px; */
48 | /* height: 30px; */
49 | background: #868686;
50 | color: #fff;
51 | border: none;
52 | border-radius: 0.5rem;
53 | }
54 |
55 | .CloseModalButton {
56 | cursor: pointer;
57 | position: absolute;
58 | top: 20px;
59 | right: 20px;
60 | width: 32px;
61 | height: 32px;
62 | padding: 0;
63 | z-index: 10;
64 | }
65 |
66 | .card2 {
67 | background-color: #fbb78d;
68 | box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2);
69 | border-radius: 0.2rem;
70 | }
71 |
72 | .card2 .modalText {
73 | font-size: 0.65em;
74 | }
75 |
76 | .red {
77 | color: #ff3c00
78 | }
--------------------------------------------------------------------------------
/src/styles/downloader.css:
--------------------------------------------------------------------------------
1 | .downloader {
2 | width: 500px;
3 | min-height: 128px;
4 | position: fixed;
5 | left: 2px;
6 | bottom: -40px;
7 | max-height: 700px;
8 | overflow-y: auto;
9 | }
10 | .downloader .card-header {
11 | color: #fff;
12 | background-color: rgb(93 11 11 / 92%);
13 | }
14 | .downloader .card .list-group {
15 | max-height: 300px;
16 | overflow: hidden;
17 | overflow-y: auto;
18 | }
19 |
--------------------------------------------------------------------------------
/src/styles/styles.css:
--------------------------------------------------------------------------------
1 | /** ---------------------------- */
2 | /** --- Code editor ------------ */
3 | /** ---------------------------- */
4 |
5 | .code-edit-container {
6 | position: relative;
7 | height: 5rem;
8 | border: 1px solid hsl(0, 0%, 60%);
9 | background-color: hsl(212, 35%, 95%);
10 | margin: 1em 0;
11 | }
12 |
13 | .code-input,
14 | .code-output {
15 | position: absolute;
16 | top: 0;
17 | left: 0;
18 | width: 100%;
19 | height: 100%;
20 | padding: 1rem;
21 | border: none;
22 | font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
23 | font-size: 0.8rem;
24 | background: transparent;
25 | white-space: pre-wrap;
26 | line-height: 1.5em;
27 | word-wrap: break-word;
28 | font-size: 1rem;
29 | }
30 |
31 | .code-input {
32 | opacity: 1;
33 | margin: 0;
34 | color: hsl(0, 0%, 40%);
35 | resize: none;
36 | height: 5rem;
37 | }
38 |
39 | .code-output {
40 | pointer-events: none;
41 | z-index: 3;
42 | margin: 0;
43 | overflow-y: auto;
44 | height: 5rem;
45 | }
46 |
47 | code {
48 | position: absolute;
49 | top: 0;
50 | left: 0;
51 | margin: 0;
52 | height: 6rem;
53 | padding: 1rem;
54 | display: block;
55 | color: hsl(0, 0%, 40%);
56 | font-size: 0.8rem;
57 | font-family: "PT Mono", monospace;
58 | }
59 |
60 | .code-mirror-wrapper {
61 | max-height: '6rem' !important;
62 | }
63 |
64 | /* This is an global styling
65 | .CodeMirror {
66 | max-height: 6rem;
67 | } */
68 |
69 | /* Code mirror syles */
70 | .cm-s-readonly {
71 | background-color: rgba(255, 255, 255, 1) !important;
72 | }
73 | .cm-s-height4rem {
74 | height: 4rem !important;
75 | width: 100% !important;
76 | padding-left: 1rem;
77 | padding-right: 1rem;
78 | }
79 | .cm-s-height35rem {
80 | height: 35.1rem !important;
81 | }
82 |
83 | .cm-s-height18rem {
84 | height: 18rem !important;
85 | }
86 |
87 | ._variableInput-inner {
88 | padding-top: 0.3rem;
89 | color: hsl(0, 0%, 18%);
90 | }
91 |
92 | /* overrides */
93 | .code-edit-container :not(pre) > code[class*="language-"],
94 | .code-edit-container pre[class*="language-"] {
95 | background: transparent;
96 | margin: 0;
97 | height: 5rem;
98 | /* background-color: seashell; */
99 | }
100 |
101 | ._queries {
102 | background-color: seashell;
103 | width: calc(100% - 0.25rem);
104 | padding-left: 1rem;
105 | padding-top: 1rem;
106 | }
107 |
108 | ._responseDisplay {
109 | margin-left: 1rem;
110 | text-align: left;
111 | font-size: 0.8em;
112 | max-height: 12rem !important;
113 | }
114 |
115 | ._responseDisplay2 {
116 | margin-left: 1rem;
117 | text-align: left;
118 | font-size: 0.8em;
119 | max-height: 6rem !important;
120 | }
121 |
122 | #responseText {
123 | margin-top: 1rem;
124 | background-color: #fbb78d;
125 | height: 39rem;
126 | border-radius: 0.5rem;
127 | }
128 |
129 | ._response2 {
130 | height: 20rem;
131 | }
132 |
133 | #responseDisplay2 {
134 | margin-left: 1rem;
135 | text-align: left;
136 | font-size: 0.8em;
137 | max-height: 24rem !important;
138 | }
139 |
140 | #responseText2 {
141 | margin-top: 1rem;
142 | background-color: #fbb78d;
143 | height: 25rem;
144 | border-radius: 0.5rem;
145 | }
146 |
147 | ._WrittenResponseDisplay {
148 | max-height: 20rem !important;
149 | }
--------------------------------------------------------------------------------
/webpack.build.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 | const BabiliPlugin = require('babili-webpack-plugin')
5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
6 |
7 | module.exports = {
8 | module: {
9 | rules: [
10 | {
11 | test: /\.css$/,
12 | use: [MiniCssExtractPlugin.loader, 'css-loader'],
13 | },
14 | {
15 | test: /\.jsx?$/,
16 | use: [{ loader: 'babel-loader', query: { compact: false } }],
17 | },
18 | {
19 | test: /\.(jpe?g|png|gif)$/,
20 | use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }],
21 | },
22 | {
23 | test: /\.(eot|svg|ttf|woff|woff2)$/,
24 | use: [
25 | { loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' },
26 | ],
27 | },
28 | ],
29 | },
30 | target: 'electron-renderer',
31 | plugins: [
32 | new HtmlWebpackPlugin({ title: 'Peach QE - Desktop Visualizer for React-Relay Applications' }),
33 | new MiniCssExtractPlugin({
34 | // Options similar to the same options in webpackOptions.output
35 | // both options are optional
36 | filename: 'bundle.css',
37 | chunkFilename: '[id].css',
38 | }),
39 | new webpack.DefinePlugin({
40 | 'process.env.NODE_ENV': JSON.stringify('production'),
41 | }),
42 | new BabiliPlugin(),
43 | ],
44 | stats: {
45 | colors: true,
46 | children: false,
47 | chunks: false,
48 | modules: false,
49 | },
50 | }
51 |
--------------------------------------------------------------------------------
/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 | const { spawn } = require('child_process')
5 |
6 | module.exports = {
7 | module: {
8 | rules: [
9 | {
10 | test: /\.css$/,
11 | use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
12 | },
13 | {
14 | exclude: /(__generate__)/,
15 | test: /\.jsx?$/,
16 | use: [{ loader: 'babel-loader', query: { compact: false } }],
17 | },
18 | {
19 | test: /\.(jpe?g|png|gif)$/,
20 | use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }],
21 | },
22 | {
23 | test: /\.(eot|svg|ttf|woff|woff2)$/,
24 | use: [
25 | { loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' },
26 | ],
27 | },
28 | ],
29 | },
30 | target: 'electron-renderer',
31 | plugins: [
32 | new HtmlWebpackPlugin({ title: 'Peach QE - Desktop Visualizer for React-Relay Applications' }),
33 | new webpack.DefinePlugin({
34 | 'process.env.NODE_ENV': JSON.stringify('development'),
35 | }),
36 | ],
37 | devtool: 'cheap-source-map',
38 | devServer: {
39 | contentBase: path.resolve(__dirname, 'dist'),
40 | stats: {
41 | colors: true,
42 | chunks: false,
43 | children: false,
44 | },
45 | before() {
46 | spawn('electron', ['.'], {
47 | shell: true,
48 | env: process.env,
49 | stdio: 'inherit',
50 | })
51 | .on('close', (code) => process.exit(0))
52 | .on('error', (spawnError) => console.error(spawnError))
53 | },
54 | },
55 | }
56 |
--------------------------------------------------------------------------------