Repository: oslabs-beta/Yodelay
Branch: dev
Commit: 0a9b84d2c7ad
Files: 54
Total size: 108.8 KB
Directory structure:
gitextract_ayrg5r4x/
├── .babelrc
├── .gitignore
├── LICENSE.md
├── README.md
├── grpc_server/
│ ├── demo.js
│ ├── grpc_server.js
│ └── uServerStream.js
├── helloworld.proto
├── index.js
├── package.json
├── protos/
│ ├── demo.proto
│ ├── helloworld-copy.proto
│ ├── helloworld.proto
│ ├── output.proto
│ ├── route_guide.proto
│ └── uGreet.proto
├── server_client/
│ ├── helper_request_func.js
│ └── server_client.js
├── src/
│ ├── actions/
│ │ ├── changeTheme.ts
│ │ ├── index.ts
│ │ ├── test.ts
│ │ ├── updateMenu.ts
│ │ └── uploadProto.ts
│ ├── components/
│ │ ├── DropdownRequest.tsx
│ │ ├── DropdownService.tsx
│ │ ├── Editor.tsx
│ │ ├── EditorRequest.tsx
│ │ ├── EditorResponse.tsx
│ │ ├── Popup.tsx
│ │ ├── Settings.tsx
│ │ ├── TestProto.tsx
│ │ └── common/
│ │ ├── Button.tsx
│ │ └── DropdownMenu.tsx
│ ├── containers/
│ │ ├── App.tsx
│ │ ├── Body.tsx
│ │ ├── Footer.tsx
│ │ ├── Header.tsx
│ │ └── Navbar.tsx
│ ├── index.html
│ ├── index.tsx
│ ├── reducers/
│ │ ├── changeTheme.ts
│ │ ├── index.ts
│ │ ├── test.ts
│ │ ├── updateMenu.ts
│ │ └── uploadProto.ts
│ ├── sagas/
│ │ └── sagas.ts
│ └── scss/
│ ├── colours.scss
│ ├── common.scss
│ ├── index.scss
│ ├── input.scss
│ └── layout.scss
├── tsconfig.json
├── webpack.dev.ts
└── webpack.prod.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules/
/.pnp
.pnp.js
/sslcert
# testing
/coverage
# production
build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
log.txt
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2019 Tammy Tan, Cedric Theofanous, German Rovati, Jamie Highsmith, Davey Yedid
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
<p align="center">
<img src="./src/assets/logo_gif.gif" />
</p>
<p align="center">
<b>Yodelay.io </b> is a browser-based testing tool that supports all types of gRPC calls: unary, server streaming, client streaming and bi-directional streaming, and aims to provide a beautiful interface and intuitive developer experience
</p>
## Core Features ✨
- Unary Calls and Server Side Streaming Support
- Client side and Bi-directional Streaming
- Automatic gRPC Service & Method recognition
- Request Cancellation
## Planned Features 🚧
- [ ] Web Version with gRPC-WEB
- [ ] Persistent Workspace
- [ ] Electron App
- [ ] Search History
## Getting Started 🚀
For an overview of gRPC in general, checkout the official [DOCS](https://grpc.io/docs/) here. There are clear and simple walkthroughs for each of the 12 programming languages supported by gRPC. Butter, Yodelay’s furry mascot, recommends choosing your preferred language, uploading the .proto file into yodelay and then using those examples to walk through this readme.
## Installation ⚙
Fork and clone this repo:
```sh
git clone https://github.com/<yourgithubhandle>/Yodelay.git
```
```sh
cd Yodelay
```
```sh
npm install
```
<p align="center">
<img src="https://media.giphy.com/media/kBSlhxKc4xkSQcGk3x/giphy.gif" />
</p>
### Test your endpoints locally ☄
Navigate to the Yodelay folder in your terminal and run the following command:
```sh
npm start
```
NPM start will build the bundle, make it available in your browser at localhost:3000, start the client server on port 4000, and start a demo grpc server at localhost:8080
<p align="center">
<img src="https://media.giphy.com/media/hU4Vzx4IbuIo8ps6A8/giphy.gif" />
</p>
Go to your browser and enter the URL:
```sh
0.0.0.0:8080
```
To test our demo proto file, upload the demo.proto file in the /protos folder
Input the server ip address:
```sh
0.0.0.0:8080
```
Select Service from the drop down you want to test:
Select Request from the drop down menu you want to test:
Edit your input message so it matches the gRPC server fields:
Click on the Send Request button and see the results from your gRPC call!
<p align="center">
<img src="https://media.giphy.com/media/hW9ui8UcGlfVXI31hY/giphy.gif" />
</p>
## Contributing ✏️👩💻👨💻📓
We have an open door policy - all ideas, feedback, and contributions are always welcome!
Note - When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.
## Built with 💛
- Cedric Theofanous - <a href="https://github.com/CedricTheofanous">@CedricTheofanous</a>
- Davey Yedid - <a href="https://github.com/dYedid">@dYedid</a>
- Jamie Highsmith - <a href="https://github.com/JamesHighsmith">@JamesHighsmith</a>
- German Rovati - <a href="https://github.com/grovati">@grovati</a>
- Tammy Tan - <a href="https://github.com/tammytan95">@tammytan95</a>
## Developed using amazing technologies ⚛ 🐳🚢
<p float="left">
<img src="./src/assets/technologies.png" width="600"/>
</p>
## License
This project is licensed under the MIT License - see the LICENSE.md file for details.
================================================
FILE: grpc_server/demo.js
================================================
let grpc = require("grpc");
let protoLoader = require("@grpc/proto-loader");
// this is our test grpc server:
let PROTO_PATH = __dirname + "/../protos/demo.proto";
let packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const packageName = "demo";
let demo_proto = grpc.loadPackageDefinition(packageDefinition)[packageName];
// console.log(demo_proto)
// first demo function:
function YodelayWorld(call, callback) {
console.log("call: ", call);
const whenI = call.request.whenI;
callback(null, {
message: `When I yodel ${whenI} --> you yodel IiiOoo!!!! YODELAY!!! --> ________________`
});
}
// second demo:
function toLowerCase(call, callback) {
// console.log(call.request)
const value = call.request.uppercase;
const lower = value.toLowerCase();
callback(null, {
message: `When you input this uppercase string: ${value} the gRPC server runs the function and responds with this output: ${lower} <--`
});
}
// run innerRps function n times
function gRPCPermutations(call, callback) {
let n = call.request.n;
let arr = [""];
while (n > 0) {
arr = innerGRPC(arr);
n -= 1;
}
console.log(arr);
const str = JSON.stringify(arr);
console.log(str);
callback(null, {
message: str
});
function innerGRPC(arr) {
const g = arr.map(letter => `g${letter}`);
const r = arr.map(letter => `R${letter}`);
const p = arr.map(letter => `P${letter}`);
const c = arr.map(letter => `C${letter}`);
return [...g, ...r, ...p, ...c];
}
}
// server streaming example
function greetManyTimes(call, callback) {
// console.log(call);
let first_name = call.request.greeting.first_name;
// console.log(call.request)
let count = 0,
intervalID = setInterval(function() {
// var greetManyTimesResponse = new demo_proto.GreetManyTimesResponse();
let greetManyTimesResponse = {};
greetManyTimesResponse.result = first_name;
// console.log(greetManyTimesResponse.result);
// setup streaming
call.write(greetManyTimesResponse);
if (++count > 9) {
clearInterval(intervalID);
call.end(); // we have sent all messages!
}
}, 1000);
}
//Client streaming example
//In Paulo's example - he uses setInterval to make the client programmatically send requests using setInterval. After all requests are sent, on.end is called which indicates all messages are sent
//Paulo's server just listens for call.on(end)
//For us --> on client side, message should be sent added to responseStream everytime sendReq button is clicked
//Upon user clicking end stream button, call.end happens and server sends back response
//long greet response = "whew that was a long one - you yodeled xxx number of times!"
function longGreet(call, callback) {
let greetArr = [];
let greet = call.request.greet;
//on receiving data, push greeting into array
call.on("data", greet => {
greetArr.push(greet);
});
//upon ending, return response
call.on("end", greet => {
let numOfYodel = greetArr.length - 1;
let LongGreetResponse = `You Yodeled ${numOfYodel} of times!`;
console.log("client streaming has ended");
//or should this be ws.send? it needs to go to front end
call.write(LongGreetResponse);
});
}
// bidi streaming example
// async function sleep(interval) {
// return new Promise(resolve => {
// setTimeout(() => resolve(), interval);
// });
// }
// async function greetEveryone(call, callback) {
// call.on("data", response => {
// // let firstName = call.request.greeting.first_name
// // let lastName = call.request.greeting.last_name
// // let fullName = firstName + ' ' + lastName
// console.log('here is the data stupid ', response)
// // console.log("Hello " + fullName);
// });
// call.on("error", error => {
// console.error(error);
// });
// call.on("end", () => {
// console.log("Server The End...");
// });
// for (let i = 0; i < 10; i++) {
// call.write({ result: 'Tammy Tan' });
// await sleep(1000);
// }
// call.end();
// }
function main() {
let server = new grpc.Server();
server.addService(demo_proto.itIsDemoTimeYodelay.service, {
YodelayWorld,
toLowerCase,
gRPCPermutations,
greetManyTimes,
longGreet
// greetEveryone
});
server.bind("0.0.0.0:8080", grpc.ServerCredentials.createInsecure());
server.start();
}
main();
console.log(`gRPC💥 'n on port: 0.0.0.0:8080`);
================================================
FILE: grpc_server/grpc_server.js
================================================
let grpc = require('grpc');
let protoLoader = require('@grpc/proto-loader');
// this is our test grpc server:
let PROTO_PATH = __dirname + '/../protos/helloworld.proto';
// const port = '0.0.0.0:50051';
let packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
//
const packageName = 'helloaworld'
let hello_proto = grpc.loadPackageDefinition(packageDefinition)[packageName];
/**
* Implements the SayHello RPC method.
*/
let url;
function sayHello(call, callback) {
console.log('------rpc function call------')
console.log(`Hi! I'm the gRPC server`)
// items coning in from the express server:
console.log('gRPC incoming call request: ', call.request)
url = call.request.url;
let package = call.request.package;
let serviceInput = call.request.serviceInput;
let requestInput = call.request.requestInput;
let messageInput = call.request.messageInput;
let protoFile = call.request.protoFile;
let protoDescriptor = call.request.protoDescriptor;
// callback(null, {message: 'Hello ' + package + `! This was a call to port ${url} requesting the ${serviceInput} in ${protoFile} with the message of: ${messageInput}`});
callback(null, {
message: `This was a call to ${url} using the ${packageName} package with the ${serviceInput} that contains the ${requestInput} method with the message of: ${messageInput}. We were able to get this from parsing ${protoFile} with the built in ${protoDescriptor}.`
});
// console.log('after grpc server')
}
/**
* Starts an RPC server that receives requests for the Greeter service at the
* sample server port
*/
function main() {
let server = new grpc.Server();
server.addService(hello_proto.YodelayAPI.service,
{ sayHello });
console.log('grpc main url: ', url);
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
server.start();
}
main();
console.log(`gRPC💥 'n on port: 0.0.0.0:50051 (${url})`)
================================================
FILE: grpc_server/uServerStream.js
================================================
const fs = require("fs");
const grpc = require("grpc");
const protoLoader = require('@grpc/proto-loader');
let PROTO_PATH = __dirname + '/../protos/uGreet.proto';
let packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const packageName = 'greet'
let greets = grpc.loadPackageDefinition(packageDefinition)[packageName];
function greetManyTimes(call, callback) {
let firstName = call.request.getGreeting().getFirstName();
let count = 0,
intervalID = setInterval(function () {
let greetManyTimesResponse = new greets.GreetManyTimesResponse();
greetManyTimesResponse.setResult(firstName);
// setup streaming
call.write(greetManyTimesResponse);
if (++count > 9) {
clearInterval(intervalID);
call.end(); // we have sent all messages!
}
}, 1000);
}
function main() {
// let credentials = grpc.ServerCredentials.createSsl(
// fs.readFileSync("../certs/ca.crt"),
// [
// {
// cert_chain: fs.readFileSync("../certs/server.crt"),
// private_key: fs.readFileSync("../certs/server.key")
// }
// ],
// true
// );
let server = new grpc.Server();
server.addService(greets.service, {
greetManyTimes: greetManyTimes,
});
server.bind("127.0.0.1:50051", grpc.ServerCredentials.createInsecure());
server.start();
console.log("Server running on port 127.0.0.1:50051");
}
main();
================================================
FILE: helloworld.proto
================================================
syntax = "proto3";
// option java_multiple_files = true;
// option java_package = "io.grpc.examples.helloworld";
// option java_outer_classname = "HelloWorldProto";
// option objc_class_prefix = "HLW";
package helloaworld;
// The greeting service definition.
service YodelayAPI {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string port = 1;
string packageName = 2;
string service = 3;
string message = 4;
string protoObject = 5;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
================================================
FILE: index.js
================================================
================================================
FILE: package.json
================================================
{
"name": "yodelay",
"version": "1.0.0",
"description": "Yodelay source code.",
"repository": "",
"main": "index.js",
"scripts": {
"start": "concurrently --kill-others \"npm run src-dev\" \"npm run start-client\" \"npm run grpc\"",
"start-client": "INSECURE_PORT=4000 nodemon server_client/server_client.js",
"start-client-noenv": "nodemon server_client/server_client.js",
"grpc": "nodemon grpc_server/demo.js",
"src-dev": "webpack-dev-server --config webpack.dev.ts --env.API_PORT=4000 --env.API_HOST=localhost --env.API_PROTOCOL=http",
"src-build": "webpack --config webpack.prod.ts",
"server-dev": "ts-node-dev --respawn --transpileOnly ./server/index.ts | ./node_modules/.bin/pino-pretty -t SYS:standard -c",
"server-local": "MODE=DEVELOPMENT PORT=8443 INSECURE_PORT=8000 CORS_URL=http://localhost:3000 HOST=http://localhost:3000 npm run server-dev",
"server-test": "MODE=TEST PORT=8443 INSECURE_PORT=8000 CORS_URL=http://localhost:3000 HOST=http://localhost:3000 npm run server-dev",
"server": "ts-node-dev --respawn --transpileOnly ./server/index.ts | ./node_modules/.bin/pino-pretty -t SYS:standard -c > log.txt 2>&1",
"test": "npm run jest",
"jest": "jest test/ --coverage --runInBand"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@grpc/proto-loader": "^0.5.3",
"@testing-library/react": "^9.3.3",
"@types/classnames": "^2.2.9",
"@types/enzyme": "^3.10.3",
"@types/express": "^4.17.0",
"@types/express-pino-logger": "^4.0.2",
"@types/jest": "^24.0.23",
"@types/numeral": "0.0.26",
"@types/react-redux": "^7.1.1",
"@types/react-router-dom": "^5.1.0",
"@types/uuid": "^3.4.6",
"@types/uuidv4": "^5.0.0",
"ace-builds": "^1.4.7",
"ag-grid-community": "^22.1.0",
"ag-grid-react": "^22.1.0",
"antd": "^3.26.5",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"body-parser": "^1.19.0",
"concurrently": "^5.0.2",
"cookie-parser": "^1.4.4",
"cors": "^2.8.5",
"css-loader": "^3.0.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"esm": "^3.2.25",
"express": "^4.17.1",
"express-http-to-https": "^1.1.4",
"express-pino-logger": "^4.0.0",
"express-ws": "^4.0.0",
"file-loader": "^5.0.2",
"google-protobuf": "^3.11.2",
"grpc": "^1.24.2",
"grpc-promise": "^1.4.0",
"grpc-tools": "^1.8.1",
"immutable": "^4.0.0-rc.12",
"isomorphic-fetch": "^2.2.1",
"jest": "^24.9.0",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.15",
"node-fetch": "^2.6.0",
"node-sass": "^4.13.0",
"nodemon": "^2.0.2",
"popper": "^1.0.1",
"protoc-gen-grpc": "^1.3.8",
"react": "^16.8.6",
"react-ace": "^8.0.0",
"react-dom": "^16.8.6",
"react-hot-loader": "^4.12.18",
"react-pivot": "^4.1.0",
"react-redux": "^7.1.0",
"react-router-dom": "^5.1.2",
"react-vis": "^1.11.7",
"redux": "^4.0.1",
"redux-devtools-extension": "^2.13.8",
"redux-saga": "^0.16.0",
"reselect": "^4.0.0",
"sass-loader": "^8.0.0",
"style-loader": "^0.23.1",
"timm": "^1.6.1",
"ts-jest": "^24.2.0",
"ts-node-dev": "^1.0.0-pre.40",
"uuidv4": "^4.0.0",
"ws": "^7.2.1"
},
"devDependencies": {
"@babel/core": "^7.7.5",
"@babel/preset-env": "^7.7.6",
"@babel/preset-react": "^7.7.4",
"@types/html-webpack-plugin": "^3.2.0",
"@types/lodash": "^4.14.136",
"@types/react": "^16.8.22",
"@types/react-dom": "^16.8.4",
"@types/react-plaid-link": "^1.3.0",
"@types/webpack": "^4.4.34",
"awesome-typescript-loader": "^5.2.1",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
"html-webpack-plugin": "^3.2.0",
"ts-node": "^8.3.0",
"typescript": "^3.6.2",
"url-loader": "^3.0.0",
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5",
"webpack-dev-server": "^3.7.2"
}
}
================================================
FILE: protos/demo.proto
================================================
syntax = "proto3";
package demo;
// This service implements a simple guestbook
service itIsDemoTimeYodelay {
// first hello world test
rpc YodelayWorld (Yodelay) returns (IiiOoo) {
}
// second calls a function that reponsed
rpc toLowerCase (UpperCaseRequest) returns (LowerCaseResponse) {
}
// List existing posts
rpc gRPCPermutations (PermutationRequest) returns (PermutationResponse) {
}
//unary API
rpc Greet (GreetRequest) returns (GreetResponse) {};
//Server streaming API
rpc GreetManyTimes (GreetManyTimesRequest) returns ( stream GreetManyTimesResponse) {};
// Client Streaming
rpc LongGreet (stream LongGreetRequest) returns (LongGreetResponse) {};
// BiDi Streaming
rpc GreetEveryone (stream GreetEveryoneRequest) returns ( stream GreetEveryoneResponse) {};
}
// ! first test for the hello world demo:
message Yodelay {
string whenI = 1;
}
message IiiOoo {
string message = 1;
}
// ! second test for unique array:
message UpperCaseRequest {
string uppercase = 1;
}
message LowerCaseResponse {
string message = 1;
}
// third test:
message PermutationRequest {
int32 n = 1;
}
message PermutationResponse {
string message = 1;
}
//Greet Mesages
message GreetEveryoneRequest {
Greeting greet = 1;
}
message GreetEveryoneResponse {
string result = 1;
}
message LongGreetRequest {
Greeting greet = 1;
}
message LongGreetResponse {
string result = 1;
}
message GreetManyTimesRequest {
Greeting greeting = 1;
}
message GreetManyTimesResponse {
string result = 1;
}
message Greeting {
string first_name = 1;
string last_name = 2;
}
message GreetRequest {
Greeting greeting = 1;
}
message GreetResponse {
string result = 1;
}
================================================
FILE: protos/helloworld-copy.proto
================================================
syntax = "proto3";
// option java_multiple_files = true;
// option java_package = "io.grpc.examples.helloworld";
// option java_outer_classname = "HelloWorldProto";
// option objc_class_prefix = "HLW";
package helloaworld;
// The greeting service definition.
service YodelayAPI {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain (HelloRequestAGAIN) returns (HelloReplyAGAIN) {}
// rpc SayHelloAgainAgain (HelloRequest) returns (HelloReply) {}
}
// The SECOND greeting service definition.
service servTWO {
// Sends a greeting
rpc SayHelloTWO (HelloRequestTWO) returns (HelloReplyTWO) {}
rpc SayHelloAgainTWO (HelloRequestTWOagain) returns (HelloReplyTWOagain) {}
// rpc SayHelloAgainAgain (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string port = 1;
string packageName = 2;
string service = 3;
string message = 4;
string protoObject = 5;
int32 integer = 6;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
int32 integer = 2;
}
// The AGAIN request message containing the user's name.
message HelloRequestAGAIN {
string portAGAIN = 1;
bytes bytes = 2;
int64 int64 = 3;
float float = 4;
bool bool = 5;
int32 integerAGAIN = 6;
}
// The AGAIN response message containing the greetings
message HelloReplyAGAIN {
string messageAGAIN = 1;
int32 integerAGAIN = 2;
}
// The SECOND request message containing the user's name.
message HelloRequestTWO {
string portTWO = 1;
double double = 2;
uint32 uint32 = 3;
uint64 uint64 = 4;
sint32 sint32 = 5;
sint64 sint64 = 6;
}
// The SECOND response message containing the greetings
message HelloReplyTWO {
string messageTWO = 1;
int32 integerTWO = 2;
}
// The SECOND AGAIN request message containing the user's name.
message HelloRequestTWOagain {
fixed32 fixed32 = 1;
fixed64 fixed64 = 2;
sfixed32 sfixed32 = 3;
sfixed64 sfixed64 = 4;
string protoObjectTWOagain = 5;
int32 integerTWOagain = 6;
}
// The SECOND AGAIN response message containing the greetings
message HelloReplyTWOagain {
string messageTWOagain = 1;
int32 integerTWOagain = 2;
}
================================================
FILE: protos/helloworld.proto
================================================
syntax = "proto3";
// option java_multiple_files = true;
// option java_package = "io.grpc.examples.helloworld";
// option java_outer_classname = "HelloWorldProto";
// option objc_class_prefix = "HLW";
package helloaworld;
// The greeting service definition.
service YodelayAPI {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
// rpc SayHelloAgainAgain (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string url = 1;
string packageName = 2;
string serviceInput = 3;
string requestInput = 4;
string messageInput = 5;
string protoFile = 6;
string protoDescriptor = 7;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
================================================
FILE: protos/output.proto
================================================
syntax = "proto3";
package demo;
// This service implements a simple guestbook
service itIsDemoTimeYodelay {
// first hello world test
rpc YodelayWorld (Yodelay) returns (IiiOoo) {
}
// second calls a function that reponsed
rpc toLowerCase (UpperCaseRequest) returns (LowerCaseResponse) {
}
// List existing posts
rpc gRPCPermutations (PermutationRequest) returns (PermutationResponse) {
}
//unary API
rpc Greet (GreetRequest) returns (GreetResponse) {};
//Server streaming API
rpc GreetManyTimes (GreetManyTimesRequest) returns ( stream GreetManyTimesResponse) {};
// Client Streaming
rpc LongGreet (stream LongGreetRequest) returns (LongGreetResponse) {};
// BiDi Streaming
rpc GreetEveryone (stream GreetEveryoneRequest) returns ( stream GreetEveryoneResponse) {};
}
// ! first test for the hello world demo:
message Yodelay {
string whenI = 1;
}
message IiiOoo {
string message = 1;
}
// ! second test for unique array:
message UpperCaseRequest {
string uppercase = 1;
}
message LowerCaseResponse {
string message = 1;
}
// third test:
message PermutationRequest {
int32 n = 1;
}
message PermutationResponse {
string message = 1;
}
//Greet Mesages
message GreetEveryoneRequest {
Greeting greet = 1;
}
message GreetEveryoneResponse {
string result = 1;
}
message LongGreetRequest {
Greeting greet = 1;
}
message LongGreetResponse {
string result = 1;
}
message GreetManyTimesRequest {
Greeting greeting = 1;
}
message GreetManyTimesResponse {
string result = 1;
}
message Greeting {
string first_name = 1;
string last_name = 2;
}
message GreetRequest {
Greeting greeting = 1;
}
message GreetResponse {
string result = 1;
}
================================================
FILE: protos/route_guide.proto
================================================
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
option objc_class_prefix = "RTG";
package routeguide;
// Interface exported by the server.
service RouteGuide {
// A simple RPC.
//
// Obtains the feature at a given position.
//
// A feature with an empty name is returned if there's no feature at the given
// position.
rpc GetFeature(Point) returns (Feature) {}
// A server-to-client streaming RPC.
//
// Obtains the Features available within the given Rectangle. Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
// A client-to-server streaming RPC.
//
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
// A Bidirectional streaming RPC.
//
// Accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
// A latitude-longitude rectangle, represented as two diagonally opposite
// points "lo" and "hi".
message Rectangle {
// One corner of the rectangle.
Point lo = 1;
// The other corner of the rectangle.
Point hi = 2;
}
// A feature names something at a given point.
//
// If a feature could not be named, the name is empty.
message Feature {
// The name of the feature.
string name = 1;
// The point where the feature is detected.
Point location = 2;
}
// A RouteNote is a message sent while at a given point.
message RouteNote {
// The location from which the message is sent.
Point location = 1;
// The message to be sent.
string message = 2;
}
// A RouteSummary is received in response to a RecordRoute rpc.
//
// It contains the number of individual points received, the number of
// detected features, and the total distance covered as the cumulative sum of
// the distance between each point.
message RouteSummary {
// The number of points received.
int32 point_count = 1;
// The number of known features passed while traversing the route.
int32 feature_count = 2;
// The distance covered in metres.
int32 distance = 3;
// The duration of the traversal in seconds.
int32 elapsed_time = 4;
}
================================================
FILE: protos/uGreet.proto
================================================
syntax = "proto3";
package greet;
service GreetService {
//unary API
rpc Greet (GreetRequest) returns (GreetResponse) {};
//Server streaming API
rpc GreetManyTimes (GreetManyTimesRequest) returns ( stream GreetManyTimesResponse) {};
// Client Streaming
rpc LongGreet (stream LongGreetRequest) returns (LongGreetResponse) {};
// BiDi Streaming
rpc GreetEveryone (stream GreetEveryoneRequest) returns ( stream GreetEveryoneResponse) {};
}
message GreetEveryoneRequest {
Greeting greet = 1;
}
message GreetEveryoneResponse {
string result = 1;
}
message LongGreetRequest {
Greeting greet = 1;
}
message LongGreetResponse {
string result = 1;
}
message GreetManyTimesRequest {
Greeting greeting = 1;
}
message GreetManyTimesResponse {
string result = 1;
}
message Greeting {
string first_name = 1;
string last_name = 2;
}
message GreetRequest {
Greeting greeting = 1;
}
message GreetResponse {
string result = 1;
}
================================================
FILE: server_client/helper_request_func.js
================================================
const grpc = require("grpc");
const protoLoader = require("@grpc/proto-loader");
const grpc_promise = require("grpc-promise");
const fs = require("fs");
const { EventEmitter } = require("events");
// input: .proto file
// output: {protoFile: "the text of the proto file", definition: {}, package: '', protoDescriptor: {}, services: [{}, {}, {}]}
async function parseProto(uploadParsedReqBody) {
// MESSAGE FIELDS:
console.log("-----Start Parsing Proto-----");
// the proto object is where we are passed in the .proto file from the server_client
// we then take this object and write it to the temp output.proto file in the proto folder:
let output = {};
const protoFile = uploadParsedReqBody;
output.protoFile = protoFile;
// WRITE TO TEMP .PROTO
// now let's write our protoObject string to the output.proto file:
fs.writeFileSync("./protos/output.proto", protoFile, "utf8", function (err) {
if (err) {
console.log("An error occurred while writing JSON Object to File.");
return console.log(err);
}
console.log("JSON file has been saved.");
});
// BUILD DEFINITION AND DESCRIPTOR:
// now we have a path for our proto:
const PROTO_PATH = __dirname + "/../protos/output.proto";
// and a config object:
const CONFIG_OBJECT = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
};
// now that the file is written we want to create our package definition:
const packageDefinition = protoLoader.loadSync(PROTO_PATH, CONFIG_OBJECT);
output.definition = packageDefinition;
// let's use the package definintion to create our descriptor:
const descriptorPre = grpc.loadPackageDefinition(packageDefinition);
// this is how you grab the .proto file package name:
const protoPackageName = Object.keys(descriptorPre)[0];
const descriptor = descriptorPre[protoPackageName];
output.package = protoPackageName;
output.protoDescriptor = descriptor;
// Creating the services object, which includes the various services, methods, messages, and message fields/types
const servicesObj = {};
for (let [service, serviceValue] of Object.entries(descriptor)) {
if (typeof serviceValue === "function") {
servicesObj[service] = {};
for (let [serviceMethodName, serviceMethodValue] of Object.entries(
serviceValue.service
)) {
const isMethodRequestStreaming = serviceMethodValue.requestStream;
const isMethodResponseStreaming = serviceMethodValue.responseStream;
let streamingType = "unary";
if (isMethodResponseStreaming) {
streamingType = "serverStreaming";
}
if (isMethodRequestStreaming) {
streamingType = "clientStreaming";
}
if (isMethodRequestStreaming && isMethodResponseStreaming) {
streamingType = "bidiStreaming";
}
const messageName = serviceMethodValue.requestType.type.name;
const messageFieldsRawData = serviceMethodValue.requestType.type.field;
servicesObj[service][serviceMethodName] = {};
servicesObj[service][serviceMethodName][messageName] = {};
servicesObj[service][serviceMethodName]["type"] = streamingType;
for (let messageInfo of messageFieldsRawData) {
console.log('messageInfo:: ', messageInfo)
const messageField = messageInfo.name;
let messageFieldType;
if (messageInfo.typeName !== '') {
messageFieldType = messageInfo.typeName;
} else {
messageFieldType = messageInfo.type;
}
servicesObj[service][serviceMethodName][messageName][
messageField
] = messageFieldType;
}
}
}
}
output.services = servicesObj;
return output;
}
class GrpcRequestClass extends EventEmitter {
constructor(websocket) {
super();
this.ws = websocket;
this.url = undefined
this.serviceInput = undefined;
this.messageInput = undefined;
this.requestInput = undefined;
this.package = undefined;
this.protoFile = undefined;
this.streamType = undefined;
this._call = undefined;
}
sendInit (reqbody) {
this.url = reqbody.url;
this.serviceInput = reqbody.serviceInput;
this.messageInput = reqbody.messageInput;
this.requestInput = reqbody.requestInput;
this.package = reqbody.package;
this.protoFile = reqbody.protoFile;
this.streamType = reqbody.requestInput.streamType;
this._call = undefined;
fs.writeFileSync("./protos/output.proto", this.protoFile, "utf8", function (err) {
if (err) {
console.log("An error occurred while writing JSON Object to File.");
return console.log(err);
}
console.log("JSON file has been saved.");
});
const PROTO_PATH = __dirname + "/../protos/output.proto";
const CONFIG_OBJECT = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
};
const packageDefinition = protoLoader.loadSync(PROTO_PATH, CONFIG_OBJECT);
let protoPackageName2 = Object.keys(packageDefinition)[0].split(".")[0];
let packageDefinitionName = Object.keys(packageDefinition)[0];
const descriptor = grpc.loadPackageDefinition(packageDefinition)[
protoPackageName2
];
let servicePackage;
try {
servicePackage = new descriptor[this.serviceInput](
this.url,
grpc.credentials.createInsecure()
);
} catch {
console.log('error creating servicePackage (descriptor) in sendInit func.')
}
function round(value, precision) {
var multiplier = Math.pow(10, precision || 0);
return Math.round(value * multiplier) / multiplier;
}
let ws = this.ws;
let messageInput;
try {
messageInput = JSON.parse(this.messageInput)
} catch {
console.log('error JSON parsing messageInput in sendInit')
}
let requestInput = this.requestInput;
let streamType = this.streamType;
if (streamType === "unary") {
// UNARY
let reqTime = process.hrtime();
try {
servicePackage[requestInput.methodName](messageInput, function (
err,
feature
) {
if (err) {
let unaryError = JSON.stringify(err)
console.log(unaryError)
ws.send(unaryError);
} else {
let resTime = process.hrtime();
let resTimeSec = resTime[0] - reqTime[0];
let resTimeMs = round(resTime[1] / 1000000 - reqTime[1] / 1000000, 2);
let resTimeStr = `Response Time: ${resTimeSec}s ${resTimeMs}ms`;
let message = '';
try {
message = JSON.stringify(feature);
ws.send(message);
} catch {
console.log('error JSON parsing unary gRPC response');
ws.send('error JSON parsing unary gRPC response');
}
}
});
} catch {
console.log('error in streamType === "unary"')
}
return this;
} else if (requestInput.streamType === "serverStreaming") {
// STREAMING
let reqTime = process.hrtime();
let call;
try {
call = servicePackage[requestInput.methodName](messageInput);
} catch {
console.log('error creating call - servicePackage')
}
this._call = call;
try {
call.on("data", function (feature) {
let resTime = process.hrtime();
let resTimeSec = resTime[0] - reqTime[0];
let resTimeMs = round(resTime[1] / 1000000 - reqTime[1] / 1000000, 2);
let resTimeStr = `Response Time: ${resTimeSec}s ${resTimeMs}ms`;
let message = JSON.stringify(feature);
ws.send(message);
});
call.on("end", function () {
ws.send('server streaming has ended')
});
call.on("error", function (e) {
() => {
ws.send('server streaming ending')
}
});
} catch {
console.log('server streaming ERROR')
ws.send('server streaming ERROR')
}
} else if (requestInput.streamType === "clientStreaming") {
//////// CLIENT STREAMING //////////
//receive data from gRPC demo server (resulting from call.write)
//link to the connection of the grpc server, must remain on clientStreaming
let reqTime = process.hrtime();
const call = servicePackage[requestInput.methodName](function (error, feature) {
if(error) {
let clientStreamingError = JSON.stringify(error)
console.log(clientStreamingError)
ws.send(clientStreamingError);
}
ws.send(JSON.stringify(feature))
});
try {
call.write(messageInput)
this._call = call;
call.on("data", function(feature) {
let resTime = process.hrtime();
let resTimeSec = resTime[0] - reqTime[0];
let resTimeMs = round(resTime[1] / 1000000 - reqTime[1] / 1000000, 2);
let resTimeStr = `Response Time: ${resTimeSec}s ${resTimeMs}ms`;
let message = JSON.stringify(feature);
ws.send(message);
});
call.on("end", function() {
ws.send('server has ended the client streaming')
});
call.on("error", function(e) {
// An error has occurred and the stream has been closed.
let clientStreamError = JSON.stringify(e)
ws.send('the following error occurred in the gRPC server: ', clientStreamError)
});
} catch {
console.log('Error in client-streaming')
ws.send('Error in client-streaming')
}
} else if (requestInput.streamType === 'bidiStreaming'){
let reqTime = process.hrtime();
let call = servicePackage[requestInput.methodName]();
this._call = call;
call.on("data", function (feature) {
let resTime = process.hrtime();
let resTimeSec = resTime[0] - reqTime[0];
let resTimeMs = round(resTime[1] / 1000000 - reqTime[1] / 1000000, 2);
let resTimeStr = `Response Time: ${resTimeSec}s ${resTimeMs}ms`;
let message = JSON.stringify(feature);
ws.send(message);
});
call.on("end", function () {
ws.send('server has ended the bidirectional streaming')
});
call.on("error", function (e) {
let bidiError = JSON.stringify(e)
ws.send('the following error occurred in the gRPC server: ', bidiError)
});
}
}
}
module.exports = {
GrpcRequestClass,
parseProto
};
================================================
FILE: server_client/server_client.js
================================================
const express = require("express");
const cookieParser = require("cookie-parser");
const bodyParser = require("body-parser");
const cors = require("cors");
const { parseProto, GrpcRequestClass } = require("./helper_request_func");
const app = express();
const expressWs = require("express-ws")(app);
const PORT = process.env.PORT || 443;
const INSECURE_PORT = process.env.INSECURE_PORT || 80;
const MODE = process.env.MODE || "PRODUCTION";
// export const HOST = process.env.HOST || 'https://mypilea.com'
const port = INSECURE_PORT;
app.use(cors());
app.use(bodyParser.text());
app.use(cookieParser());
app.use(express.static("build"));
// app.get("/", (req, res) => res.send("🍻 Yodelay World 🍻"));
// * UPLOAD:
app.post("/upload", async (req, res) => {
const parsedReqBody = JSON.parse(req.body);
let output = await parseProto(parsedReqBody);
res.json(output);
});
//Listens for messages
app.ws("/websocket", function(ws, req) {
const grpcRequestClass = new GrpcRequestClass(ws);
try {
ws.on("message", function(msg) {
let parsedReqBody;
try {
parsedReqBody = JSON.parse(msg);
} catch {
ws.send("message", "error parsing JSON in ws.on message");
}
if (parsedReqBody.wsCommand === "sendInit") {
console.log("sendInit");
grpcRequestClass.sendInit(parsedReqBody);
} else if (parsedReqBody.wsCommand === "push") {
console.log("push");
let messageInput;
try {
messageInput = JSON.parse(parsedReqBody.messageInput);
} catch {
console.log('error parsing messageInput in ws-router - "push"');
}
console.log("||||||||||||||||PUSH", messageInput);
grpcRequestClass._call.write(messageInput);
} else if (parsedReqBody.wsCommand === "end") {
if (parsedReqBody.requestInput.streamType === "serverStreaming") {
grpcRequestClass._call.cancel();
console.log("Cancel");
} else {
grpcRequestClass._call.end();
console.log("end");
}
}
});
} catch {
console.log("error in ws");
}
});
app.use((req, res) => {
res.status(404).send("Page Not Found");
});
// Global error handling:
app.use(function(err, req, res, next) {
const defaultError = {
log: "Express error handler caught unknown middleware error",
status: 400,
message: { err: "An error occurred" }
};
const newErrObj = Object.assign(defaultError, err);
console.log(newErrObj);
res.status(newErrObj.status).json(newErrObj.message);
});
app.listen(port, () =>
console.log(` 👽 invasion happening on port: ${port} `)
);
================================================
FILE: src/actions/changeTheme.ts
================================================
//Define action type
export const CHANGE_THEME = "CHANGE_THEME";
//Define shape of action type
export interface changeTheme {
type: typeof CHANGE_THEME;
payload: string;
}
//Groups all action types so that they can be referenced in the reducer files via one umbrella type -- basically, we're trying to make sure that any given reducer can only accept certain action types in the switch/case statement
export type changeThemeAction = changeTheme;
export const changeThemeActionCreator = (payloadObj: string): changeTheme => {
return {
type: CHANGE_THEME,
payload: payloadObj
};
};
================================================
FILE: src/actions/index.ts
================================================
/****************************************single source of all creators and action types***************************** */
export * from "./test";
export * from "./uploadProto";
export * from "./updateMenu";
export * from "./changeTheme";
// export * from './actionTypes'
================================================
FILE: src/actions/test.ts
================================================
// Consolidate actionTypes with this file. Rename this file from actionCreators to increment.ts (we should mirror increment.ts in the reducers file). Remove below line
// import {INCREMENT} from './actionTypes'
/****************************create action type*************************** */
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
/*****************************************define types for action obj************** */
// defines
export interface incrementAction {
type: typeof INCREMENT
payload: number
}
export interface decrementAction {
type: typeof DECREMENT
payload: number
}
// we combine the increment and decrement interface types into one type so that this one type can be used elsewhere, like in /reducers/increment
export type incrementActions = incrementAction | decrementAction
/**********************************makes available outside of file ********** */
//we can get rid of export type by just exporting the interface. Remove below line
// export type incrementActionType = incrementAction
export const incrementActionCreator = (incrementNum: number): incrementAction => {
return {
type: INCREMENT,
payload: incrementNum
}
}
export const decrementActionCreator = (decrementNum: number): decrementAction => {
return {
type: DECREMENT,
payload: decrementNum
}
}
================================================
FILE: src/actions/updateMenu.ts
================================================
//Define action type
export const LOAD_SERVICE_OPTIONS = 'LOAD_SERVICE_OPTIONS '
export const LOAD_REQUEST_OPTIONS = 'LOAD_REQUEST_OPTIONS '
//Define shape of action type
export interface loadServiceOptions {
type: typeof LOAD_SERVICE_OPTIONS;
payload: object
}
export interface loadRequestOptions {
type: typeof LOAD_REQUEST_OPTIONS;
payload: object
}
//Groups all action types so that they can be referenced in the reducer files via one umbrella type -- basically, we're trying to make sure that any given reducer can only accept certain action types in the switch/case statement
export type loadMenuAction = loadServiceOptions | loadRequestOptions
export const loadServiceActionCreator = (
payloadObj: object
): loadServiceOptions => {
return {
type: LOAD_SERVICE_OPTIONS,
payload: payloadObj
};
};
export const loadRequestActionCreator = (
payloadObj: object
): loadRequestOptions => {
return {
type: LOAD_REQUEST_OPTIONS,
payload: payloadObj
};
};
================================================
FILE: src/actions/uploadProto.ts
================================================
import { typeResponse } from "../reducers/uploadProto";
//Define action type
export const UPLOAD_PROTO = "UPLOAD_PROTO";
export const SEND_PROTO = "SEND_PROTO";
export const UPLOAD_PROTO_SUCCESSFUL = "UPLOAD_PROTO_SUCCESSFUL";
export const UPLOAD_PROTO_FAILED = "UPLOAD_PROTO_FAILED";
export const SEND_UNARY_REQUEST = "SEND_UNARY_REQUEST";
export const SET_MESSAGE = "SET_MESSAGE";
export const SET_SERVICE = "SET_SERVICE";
export const SET_URL = "SET_URL";
export const SET_REQUEST = "SET_REQUEST";
export const DISPLAY_UNARY_RESPONSE = "DISPLAY_UNARY_RESPONSE";
export const CLEAR_RESPONSE_EDITOR = "CLEAR_RESPONSE_EDITOR";
export const SHOW_POPUP = "SHOW_POPUP";
export const SET_WS_COMMAND = "SET_WS_COMMAND";
export const START_WEBSOCKET = "START_WEBSOCKET";
//Define shape of action type
//Arraybuffer is an array of bytes, representing a generic, fixed-length raw binary data buffer
export interface uploadProto {
type: typeof UPLOAD_PROTO;
payload: string | ArrayBuffer;
}
export interface sendProto {
type: typeof SEND_PROTO;
payload: string | ArrayBuffer;
}
export interface uploadProtoSuccessful {
type: typeof UPLOAD_PROTO_SUCCESSFUL;
payload: object;
}
export interface sendUnaryRequest {
type: typeof SEND_UNARY_REQUEST;
payload: any;
}
export interface displayUnaryResponse {
type: typeof DISPLAY_UNARY_RESPONSE;
payload: typeResponse;
}
export interface setMessage {
type: typeof SET_MESSAGE;
payload: string;
}
export interface setService {
type: typeof SET_SERVICE;
payload: string;
}
export interface setUrl {
type: typeof SET_URL;
payload: string;
}
export interface setRequest {
type: typeof SET_REQUEST;
payload: object;
}
export interface uploadProtoFailed {
type: typeof UPLOAD_PROTO_FAILED;
payload: string;
}
export interface clearResponseEditor {
type: typeof CLEAR_RESPONSE_EDITOR;
payload: typeResponse[];
}
export interface showPopup {
type: typeof SHOW_POPUP;
payload: boolean;
}
export interface setWsCommand {
type: typeof SET_WS_COMMAND;
payload: string;
}
export interface startWebsocket {
type: typeof START_WEBSOCKET;
payload: string;
}
//Groups all action types so that they can be referenced in the reducer files via one umbrella type -- basically, we're trying to make sure that any given reducer can only accept certain action types in the switch/case statement
export type uploadProtoAction =
| uploadProto
| sendProto
| uploadProtoSuccessful
| uploadProtoFailed
| setMessage
| setService
| setUrl
| setRequest
| sendUnaryRequest
| displayUnaryResponse
| clearResponseEditor
| showPopup
| setWsCommand
| startWebsocket
export const uploadProtoActionCreator = (
payloadObj: string | ArrayBuffer
): uploadProto => {
return {
type: UPLOAD_PROTO,
payload: payloadObj
};
};
// Do we ever use sendProtoActionCreator?
export const sendProtoActionCreator = (
payloadObj: string | ArrayBuffer
): sendProto => {
return {
type: SEND_PROTO,
payload: payloadObj
};
};
export const uploadProtoSuccessfulActionCreator = (
payloadObj: object
): uploadProtoSuccessful => {
return {
type: UPLOAD_PROTO_SUCCESSFUL,
payload: payloadObj
};
};
export const uploadProtoFailedActionCreator = (
payloadObj: string
): uploadProtoFailed => {
return {
type: UPLOAD_PROTO_FAILED,
payload: payloadObj
};
};
export const sendUnaryRequestActionCreator = (
payloadObj: any
): sendUnaryRequest => {
return {
type: SEND_UNARY_REQUEST,
payload: payloadObj
};
};
export const displayUnaryResponseActionCreator = (
payloadObj: any
): displayUnaryResponse => {
return {
type: DISPLAY_UNARY_RESPONSE,
payload: payloadObj
};
};
export const setMessageActionCreator = (payloadObj: string): setMessage => {
return {
type: SET_MESSAGE,
payload: payloadObj
};
};
export const setServiceActionCreator = (payloadObj: string): setService => {
return {
type: SET_SERVICE,
payload: payloadObj
};
};
export const setUrlActionCreator = (payloadObj: string): setUrl => {
return {
type: SET_URL,
payload: payloadObj
};
};
export const setRequestActionCreator = (payloadObj: object): setRequest => {
return {
type: SET_REQUEST,
payload: payloadObj
};
};
export const clearResponseEditorActionCreator = (
payloadObj: typeResponse[]
): clearResponseEditor => {
return {
type: CLEAR_RESPONSE_EDITOR,
payload: payloadObj
};
};
export const showPopupActionCreator = (payloadObj: boolean): showPopup => {
return {
type: SHOW_POPUP,
payload: payloadObj
};
};
export const setWsCommandActionCreator = (
payloadObj: string
): setWsCommand => {
return {
type: SET_WS_COMMAND,
payload: payloadObj
};
};
export const startWebsocketActionCreator = (
payloadObj: string
): startWebsocket => {
return {
type: START_WEBSOCKET,
payload: payloadObj
}
}
================================================
FILE: src/components/DropdownRequest.tsx
================================================
import React, { FunctionComponent } from "react";
import { setRequestActionCreator,
setMessageActionCreator,
clearResponseEditorActionCreator,
setWsCommandActionCreator
} from "../actions";
import { typeRequest } from "../reducers/uploadProto";
interface DropdownRequestProps {
parsedProtoObj: any;
className: string;
menuOptions: any;
service: string;
value: typeRequest;
setRequest: typeof setRequestActionCreator;
setMessageAction?: typeof setMessageActionCreator;
setWsCommandAction: typeof setWsCommandActionCreator;
clearResponseEditor: typeof clearResponseEditorActionCreator;
}
export const DropdownRequest: FunctionComponent<DropdownRequestProps> = props => {
{
const {
parsedProtoObj,
className,
menuOptions,
service,
setRequest,
value,
setMessageAction,
setWsCommandAction,
clearResponseEditor
} = props;
//create array of requests
let servicesArr: string[] = [];
let servicesObj: {
[index: string]: {
message: { [nestedIndex: string]: { message: string } };
};
} = {};
if (service) {
servicesArr = Object.keys(menuOptions[service]);
servicesObj = menuOptions[service];
} else {
}
return (
<div>
<select
onChange={e => {
if (e.target.value === "Select Request") {
setRequest({ methodName: "", streamType: "" });
setMessageAction("");
} else {
//user selects request method
let requestSelected = e.target.value;
//Need to update state w/ requestSelected and streamType
let streamType =
parsedProtoObj["services"][`${service}`][`${requestSelected}`]
.type;
clearResponseEditor([])
setWsCommandAction('');
setRequest({
methodName: requestSelected,
streamType: streamType
});
//Need to auto populate editor with types
const messageFieldsObj: object = Object.values(
servicesObj[e.target.value]
)[0];
const messageFieldsArrayOfTuples: string[][] = Object.entries(
messageFieldsObj
);
let editorDisplay: string = "";
for (let [field, type] of messageFieldsArrayOfTuples) {
// if (Array.isArray(type)) {
// let [definedMessageType, definedMessageName] = type;
// let definedMessageString =
// } else {
// }
let typeDisplay: any = type;
switch (type) {
case "TYPE_STRING": {
typeDisplay = '"Hello"';
break;
}
case "TYPE_INT32": {
typeDisplay = 10;
break;
}
case "TYPE_INT32": {
typeDisplay = 10;
break;
}
}
const currentStr: string = `"${field}": ${typeDisplay},\n `;
editorDisplay += currentStr;
}
editorDisplay =
"{\n " +
editorDisplay.slice(0, editorDisplay.length - 4) +
"\n}";
setMessageAction(editorDisplay);
}
}}
>
<option>Select Request</option>
{servicesArr.map((menuOptions, i) => (
<option key={i}>{menuOptions}</option>
))}
</select>
</div>
);
}
};
================================================
FILE: src/components/DropdownService.tsx
================================================
import React, { FunctionComponent } from 'react';
import { setServiceActionCreator,
startWebsocketActionCreator,
clearResponseEditorActionCreator,
setWsCommandActionCreator }
from '../actions';
interface DropdownServiceProps {
className?: string;
menuOptions?: object;
value: string;
setService: typeof setServiceActionCreator;
startWebsocket: typeof startWebsocketActionCreator;
clearResponseEditor: typeof clearResponseEditorActionCreator;
setWsCommandAction: typeof setWsCommandActionCreator;
}
export const DropdownService: FunctionComponent< DropdownServiceProps> = props => {
{
const {
className,
menuOptions,
setService,
startWebsocket,
value,
setWsCommandAction,
clearResponseEditor
} = props;
//create array of services
const servicesArr = Object.keys(menuOptions);
return (
<div>
<select
onChange={e => {
if (e.target.value === 'Select Service') {
setService('');
} else {
setService(e.target.value);
startWebsocket('string');
setWsCommandAction('');
clearResponseEditor([])
}
}}
>
<option>Select Service</option>
{servicesArr.map((menuOptions, i) => (
<option key={i}>{menuOptions}</option>
))}
</select>
</div>
);
}
};
================================================
FILE: src/components/Editor.tsx
================================================
import React, { FunctionComponent } from "react";
import { EditorRequest } from "./EditorRequest";
import { EditorResponse } from "./EditorResponse";
import { statement } from "@babel/template";
import { setMessageActionCreator } from "../actions";
import { useDispatch } from "react-redux";
import { typeResponse } from "../reducers/uploadProto";
interface editorProps {
setMessageAction?: typeof setMessageActionCreator;
data: string;
response: typeResponse[];
changeTheme: string;
}
export const Editor: FunctionComponent<editorProps> = props => {
{
const { setMessageAction, data, response, changeTheme } = props;
return (
<>
<EditorRequest
data={data}
newRequest={value => {
setMessageAction(value);
}}
changeTheme={changeTheme}
/>
<EditorResponse response={response} changeTheme={changeTheme} />
</>
);
}
};
================================================
FILE: src/components/EditorRequest.tsx
================================================
import React, { FunctionComponent } from "react";
// import AceEditor, {Command} from 'react-ace'
import AceEditor from "react-ace";
import { Tabs } from "antd";
import "ace-builds/src-noconflict/theme-monokai";
import "ace-builds/src-noconflict/theme-cobalt";
import "ace-builds/src-noconflict/theme-pastel_on_dark";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-clouds";
import "ace-builds/src-noconflict/theme-crimson_editor";
import "ace-builds/src-noconflict/theme-sqlserver";
interface RequestProps {
data?: string;
newRequest: (value: string) => void;
changeTheme: string;
}
export const EditorRequest: FunctionComponent<RequestProps> = props => {
{
const { data, newRequest, changeTheme } = props;
const editorTabKey = `editor Tab`;
//CHANGE THEME
let toggleTheme =
changeTheme === "dark-yellow" ||
changeTheme === "dark-green" ||
changeTheme === "dark-blue"
? "monokai"
: "SQL Server";
return (
<>
<div>Editor
<AceEditor
mode="json"
name="requestInput"
value={data}
onChange={newRequest}
theme={toggleTheme}
height={"250px"}
width={"100%"}
wrapEnabled
showGutter
fontSize={12}
cursorStart={2}
showPrintMargin={false}
highlightActiveLine={false}
tabSize={2}
setOptions={{
useWorker: true,
displayIndentGuides: true
}}
/>
</div>
</>
);
}
};
================================================
FILE: src/components/EditorResponse.tsx
================================================
import React, { FunctionComponent } from "react";
// import AceEditor, {Command} from 'react-ace'
import AceEditor from "react-ace";
import { Tabs } from "antd";
import { typeResponse } from "../reducers/uploadProto";
import "ace-builds/src-noconflict/theme-monokai";
import "ace-builds/src-noconflict/theme-cobalt";
import "ace-builds/src-noconflict/theme-pastel_on_dark";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-clouds";
import "ace-builds/src-noconflict/theme-crimson_editor";
import "ace-builds/src-noconflict/theme-sqlserver";
interface ResponseProps {
response: typeResponse[];
changeTheme: string;
}
export const EditorResponse: FunctionComponent<ResponseProps> = props => {
{
const defaultKey = `responseTab`;
const { response, changeTheme } = props;
let stringResponse = "";
let requestCount = 0;
let responseCount = 0;
response.forEach((element, i) => {
if( i === 0) {
responseCount = 0;
requestCount = 0;
}
if (typeof element === 'object') {
responseCount += 1;
stringResponse += `\nResponse:\n${element.message}\n`;
} else {
requestCount += 1;
stringResponse += `\nRequest:\n${element}\n`;
}
});
let responseTime = 1;
//CHANGE THEME
let toggleTheme =
changeTheme === "dark-yellow" ||
changeTheme === "dark-green" ||
changeTheme === "dark-blue"
? "monokai"
: "SQL Server";
return (
<>
<div> Log
<AceEditor
mode="json"
name="requestInput"
value={stringResponse}
width={"100%"}
height={"250px"}
theme={toggleTheme}
fontSize={12}
cursorStart={2}
showGutter
wrapEnabled
showPrintMargin={false}
highlightActiveLine={false}
tabSize={2}
readOnly={true}
setOptions={{
useWorker: true,
displayIndentGuides: true,
highlightGutterLine: false
}}
/>
</div>
</>
);
}
};
================================================
FILE: src/components/Popup.tsx
================================================
import React, { FunctionComponent } from 'react';
import { Button } from './common/Button';
import { showPopupActionCreator } from '../actions';
interface PopupProps {
popup: boolean;
toggle: typeof showPopupActionCreator;
proto: string | ArrayBuffer;
}
export const Popup: FunctionComponent<PopupProps> = (props) => {
{
const {
popup,
toggle,
proto
} = props;
const popupClass: string = popup ? 'popup-on' : 'popup-off';
// const popupClass: string = "modal popup-off";
const handleToggle = () => {
toggle(!popup);
}
return(
<div className={popupClass}>
<div className="modal-main">
<pre>{proto}</pre>
<Button onClick={handleToggle} text={'Close'}></Button>
</div>
</div>
)
}
}
================================================
FILE: src/components/Settings.tsx
================================================
import React, { FunctionComponent } from "react";
import { connect } from "react-redux";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import { Button } from "./common/Button";
import { changeTheme, themeSelector } from "../reducers/changeTheme";
import { RootState } from "../reducers";
import { changeThemeActionCreator } from "../actions";
// sets type for props
interface SettingsProps {
changeThemeAction: typeof changeThemeActionCreator;
changeTheme: string;
}
export const Settings: FunctionComponent<SettingsProps> = props => {
{
const { changeThemeAction, changeTheme } = props;
const toggleDarkYellow = () => {
changeThemeAction("dark-yellow");
};
const toggleLightYellow = () => {
changeThemeAction("light-yellow");
};
const toggleDarkGreen = () => {
changeThemeAction("dark-green");
};
const toggleDarkBlue = () => {
changeThemeAction("dark-blue");
};
return (
<div>
<div>⚡⚙ Select A Theme ⚙⚡</div>
{/* Dark Yellow Theme */}
<Button
className="button-dark-yellow"
onClick={toggleDarkYellow}
></Button>
{/* Light Yellow Theme */}
<Button
className="button-light-yellow"
onClick={toggleLightYellow}
></Button>
{/* Dark Green Theme */}
<Button
className="button-dark-green"
onClick={toggleDarkGreen}
></Button>
{/* Dark Blue Theme */}
<Button className="button-dark-blue" onClick={toggleDarkBlue}></Button>
</div>
);
}
};
export default connect(
(state: RootState) => ({
changeTheme: themeSelector(state)
}),
{
changeThemeAction: changeThemeActionCreator
}
)(Settings);
================================================
FILE: src/components/TestProto.tsx
================================================
import React, { FunctionComponent } from 'react';
import { connect } from 'react-redux';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import {
setMessageActionCreator,
setServiceActionCreator,
setUrlActionCreator,
setRequestActionCreator,
sendUnaryRequestActionCreator,
clearResponseEditorActionCreator,
showPopupActionCreator,
setWsCommandActionCreator,
startWebsocketActionCreator
} from '../actions';
import { Editor } from './Editor';
import {
typeResponse,
typeRequest,
popupSelector
} from '../reducers/uploadProto';
import { DropdownService } from './DropdownService';
import { DropdownRequest } from './DropdownRequest';
import { Button } from './common/Button';
// sets type for props
interface TestProtoProps {
parsedProtoObj: object;
serviceOptions: object;
data: string;
service: string;
url: string;
response: typeResponse[];
request: typeRequest;
wsCommand: string;
setMessageAction: typeof setMessageActionCreator;
setServiceAction: typeof setServiceActionCreator;
setUrlAction: typeof setUrlActionCreator;
setRequestAction: typeof setRequestActionCreator;
setWsCommandAction: typeof setWsCommandActionCreator;
sendUnaryRequestAction: any;
clearResponseEditor: typeof clearResponseEditorActionCreator;
proto: string | ArrayBuffer;
togglePopup: typeof showPopupActionCreator;
startWebsocketAction: typeof startWebsocketActionCreator;
popupStatus: boolean;
changeTheme: string;
}
export const TestProto: FunctionComponent<TestProtoProps> = props => {
{
const {
parsedProtoObj,
serviceOptions,
data,
response,
service,
request,
url,
proto,
wsCommand,
togglePopup,
popupStatus,
changeTheme,
setWsCommandAction,
setMessageAction,
setServiceAction,
setUrlAction,
setRequestAction,
sendUnaryRequestAction,
startWebsocketAction,
clearResponseEditor
} = props;
const handleUrlChange = (e: any) => {
setUrlAction(e.target.value);
};
// function that inovokes the action creator to clear response editor, sends grpc request information, and starts websocket connection
const handleRequestClick = (e: any) => {
if(url.length === 0) {
let value = prompt('Please enter IP address');
if(!value) {
setUrlAction('');
} else {
setUrlAction(value);
}
} else {
clearResponseEditor([]);
setWsCommandAction('sendInit');
sendUnaryRequestAction(data);
}
};
// function that invokes action creator to push messages
const handlePushClick = (e: any) => {
setWsCommandAction('push');
sendUnaryRequestAction(data);
};
// function that invokes action creator to end websocket connection
const handleEndClick = (e: any) => {
setWsCommandAction('end');
sendUnaryRequestAction('End stream');
};
const handleViewClick = (e: any) => {
if (proto === '') {
alert('upload proto file');
} else {
togglePopup(!popupStatus);
}
};
//based on state (e.g. "yellow"), pass down
const toggle = 'yellow';
//CHANGE THEME
let toggleThemeTestProto = `testProto-${changeTheme}`;
let toggleThemeInputBox = `url-input-${changeTheme}`;
let toggleThemeViewProto = `button viewProto-button-${changeTheme}`;
let pushButton;
let endButton;
let requestButton;
let showStreamingType;
let changeThemeBackground;
if (request.streamType === '' || request.streamType === 'unary') {
requestButton = (<Button className='send-button' onClick={handleRequestClick}/>);
} else if (request.streamType === 'clientStreaming' || request.streamType === 'bidiStreaming') {
if(wsCommand === '' || wsCommand === 'end') {
requestButton = (<Button className='send-button' onClick={handleRequestClick}/>);
} else {
pushButton = (<Button className='push-button' onClick={handlePushClick} />);
endButton = (<Button className='pause-button' onClick={handleEndClick} />);
}
} else if (request.streamType === 'serverStreaming') {
if(wsCommand === '' || wsCommand === 'end') {
requestButton = (<Button className='send-button' onClick={handleRequestClick}/>);
} else {
endButton = (<Button className='pause-button' onClick={handleEndClick}/>);
}
}
if (changeTheme === 'dark-yellow' || changeTheme === 'light-yellow') {
changeThemeBackground = '#f9c132';
} else if (changeTheme === 'dark-green') {
changeThemeBackground = '#50fa7b';
} else if (changeTheme === 'dark-blue') {
changeThemeBackground = '#57b5f9';
}
if (request.streamType) {
showStreamingType = (
<em
style={{ backgroundColor: changeThemeBackground, color: 'black' }}
>
{' '}
{`Type: ${request.streamType}`}
</em>
);
}
return (
<div id={toggleThemeTestProto}>
Test Your Proto File:
<div id='menu-and-view-section'>
<div className='menu-options'>
<input
className={toggleThemeInputBox}
placeholder=' IP address'
onChange={handleUrlChange}
value={url}
></input>
<DropdownService
className='service-dropdown-menu'
menuOptions={serviceOptions}
setService={setServiceAction}
startWebsocket={startWebsocketAction}
setWsCommandAction={setWsCommandAction}
clearResponseEditor={clearResponseEditor}
value={service}
></DropdownService>
<DropdownRequest
className='request-dropdown-menu'
menuOptions={serviceOptions}
service={service}
setRequest={setRequestAction}
setWsCommandAction={setWsCommandAction}
clearResponseEditor={clearResponseEditor}
value={request}
parsedProtoObj={parsedProtoObj}
setMessageAction={setMessageAction}
></DropdownRequest>
{requestButton}
{pushButton}
{endButton}
</div>
<div id="handleViewClick">
{showStreamingType}
<Button
className={toggleThemeViewProto}
text="View Proto File"
onClick={handleViewClick}
/>
</div>
</div>
<div>
<Editor
setMessageAction={setMessageAction}
data={data}
response={response}
changeTheme={changeTheme}
/>
</div>
</div>
);
}
};
export default TestProto;
================================================
FILE: src/components/common/Button.tsx
================================================
import React, { FunctionComponent } from 'react'
import { urlencoded } from 'body-parser'
interface ButtonProps {
className?: string
onClick?: (...arg: any[]) => any
text?: string
value?: number
icon?: string
}
export const Button:
FunctionComponent<ButtonProps> = props => {
{
const {
className,
onClick,
text,
value,
icon
} = props
return (
<button className={className} onClick={onClick} value={value}>{text}</button>
)
}
}
// style = { {backgroundImage: `url(${icon})`}}
================================================
FILE: src/components/common/DropdownMenu.tsx
================================================
import React, { FunctionComponent } from 'react';
interface DropdownMenuProps {
id?: string;
menuOptions?: object;
}
export const DropdownMenu: FunctionComponent<DropdownMenuProps> = props => {
{
const { id, menuOptions } = props;
//menuOptions object holds the following content:
//services: {
//service1: {request1: {message1Options}}
//service2: {request2: {message2Options}}
//}
//get array of services
const servicesArr = Object.keys(menuOptions);
let selectValue;
return (
<div>
<select
onChange={e => {
selectValue = e.target.value;
}}
>
{servicesArr.map((menuOptions, i) => (
<option key={i}>{menuOptions}</option>
))}
</select>
</div>
);
}
};
================================================
FILE: src/containers/App.tsx
================================================
import React, { FunctionComponent } from "react";
import { connect } from "react-redux";
import { RootState } from "../reducers";
import HeaderContainer, { Header } from "./Header";
import BodyContainer from "./Body";
import FooterContainer from "./Footer";
import NavbarContainer from "./Navbar";
import { Popup } from "../components/Popup";
import { Button } from "../components/common/Button";
import {
incrementActionCreator,
uploadProtoActionCreator,
uploadProtoSuccessfulActionCreator,
loadServiceActionCreator,
showPopupActionCreator,
changeThemeActionCreator
} from "../actions";
import { countSelector } from "../reducers/test";
import {
protoSelector,
parsedProtoObjSelector,
popupSelector
} from "../reducers/uploadProto";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import "../scss/index.scss";
import { serviceMenuSelector } from "../reducers/updateMenu";
import { themeSelector } from "../reducers/changeTheme";
// import { loggedInSelector } from '../reducers/login'
// sets type for props
interface AppProps {
incrementAction: typeof incrementActionCreator;
uploadProtoSuccessful: typeof uploadProtoSuccessfulActionCreator;
serviceOptions: object;
protoObjContents: object;
togglePopup: typeof showPopupActionCreator;
proto: string | ArrayBuffer;
popupStatus: boolean;
changeThemeAction: typeof changeThemeActionCreator;
changeTheme: string;
}
export const App: FunctionComponent<AppProps> = props => {
{
const {
incrementAction,
serviceOptions,
protoObjContents,
togglePopup,
proto,
popupStatus,
changeTheme
} = props;
//CHANGE THEME
let toggleThemeMainView = `main-view-${changeTheme}`;
let toggleThemeNavBar = `navbar-${changeTheme}`;
return (
//Wrap everything in Router so that nested containers/components have access to router
<Router>
{/* main-view */}
<div id={toggleThemeMainView}>
<div id={toggleThemeNavBar}>
<NavbarContainer></NavbarContainer>
</div>
<div id="app-container">
<HeaderContainer></HeaderContainer>
<BodyContainer serviceOptions={serviceOptions}></BodyContainer>
<FooterContainer></FooterContainer>
</div>
<div>
<Popup
popup={popupStatus}
toggle={togglePopup}
proto={proto}
></Popup>
</div>
{/* <Button text='enter' onClick={ () => {incrementAction(1)}} >
</Button> */}
</div>
</Router>
);
}
};
// gives the app component access to state and actions from the store
export default connect(
//if using selector
(state: RootState) => ({
test: countSelector(state),
protoContents: protoSelector(state),
serviceOptions: serviceMenuSelector(state),
protoObjContents: parsedProtoObjSelector(state),
proto: protoSelector(state),
popupStatus: popupSelector(state),
changeTheme: themeSelector(state)
}),
{
incrementAction: incrementActionCreator,
uploadProto: uploadProtoActionCreator,
uploadProtoSuccessful: uploadProtoSuccessfulActionCreator,
loadServiceOptions: loadServiceActionCreator,
togglePopup: showPopupActionCreator,
changeThemeAction: changeThemeActionCreator
}
)(App);
//if not using selector
// (state: RootState) => ({
// test: state.test
// }),
================================================
FILE: src/containers/Body.tsx
================================================
import React, { FunctionComponent, useState } from "react";
import { connect } from "react-redux";
import TestProto from "../components/TestProto";
import Settings from "../components/Settings";
import { Route } from "react-router";
import {
setMessageActionCreator,
setServiceActionCreator,
setUrlActionCreator,
setRequestActionCreator,
sendUnaryRequestActionCreator,
clearResponseEditorActionCreator,
showPopupActionCreator,
changeThemeActionCreator,
setWsCommandActionCreator,
startWebsocketActionCreator
} from "../actions";
import {
messageSelector,
serviceSelector,
urlSelector,
requestSelector,
responseSelector,
typeResponse,
typeRequest,
parsedProtoObjSelector,
protoSelector,
popupSelector,
setWsSelector
} from "../reducers/uploadProto";
import { RootState } from "../reducers";
import { themeSelector } from "../reducers/changeTheme";
import { start } from "repl";
// sets type for props
interface BodyProps {
parsedProtoObj: object;
serviceOptions: object;
setMessageAction: typeof setMessageActionCreator;
setUrlAction: typeof setUrlActionCreator;
setRequestAction: typeof setRequestActionCreator;
setServiceAction: typeof setServiceActionCreator;
sendUnaryRequestAction?: typeof sendUnaryRequestActionCreator;
clearResponseEditorAction: typeof clearResponseEditorActionCreator;
setWsCommandAction: typeof setWsCommandActionCreator;
startWebsocketAction: typeof startWebsocketActionCreator;
selectMessage: string;
selectService: string;
selectUrl: string;
selectRequest: typeRequest;
selectResponse: typeResponse[];
selectWsCommand: string;
proto: string | ArrayBuffer;
togglePopup?: typeof showPopupActionCreator;
popupStatus?: boolean;
changeThemeAction: typeof changeThemeActionCreator;
changeTheme: string;
}
export const Body: FunctionComponent<BodyProps> = props => {
{
const {
parsedProtoObj,
serviceOptions,
setMessageAction,
setServiceAction,
setUrlAction,
setRequestAction,
setWsCommandAction,
sendUnaryRequestAction,
clearResponseEditorAction,
startWebsocketAction,
selectWsCommand,
selectMessage,
selectService,
selectUrl,
selectRequest,
selectResponse,
togglePopup,
popupStatus,
proto,
changeTheme
} = props;
return (
<div>
<Route exact path="/">
<TestProto
parsedProtoObj={parsedProtoObj}
serviceOptions={serviceOptions}
setMessageAction={setMessageAction}
setRequestAction={setRequestAction}
setServiceAction={setServiceAction}
setUrlAction={setUrlAction}
setWsCommandAction={setWsCommandAction}
sendUnaryRequestAction={sendUnaryRequestAction}
clearResponseEditor={clearResponseEditorAction}
startWebsocketAction={startWebsocketAction}
url={selectUrl}
data={selectMessage}
request={selectRequest}
response={selectResponse}
togglePopup={togglePopup}
popupStatus={popupStatus}
proto={proto}
service={selectService}
changeTheme={changeTheme}
wsCommand={selectWsCommand}
></TestProto>
</Route>
<Route path="/settings">
<Settings>Settings</Settings>
</Route>
</div>
);
}
};
export default connect(
// gives the app component access to state and actions from the store
// export default connect(
// //using selector
(state: RootState) => ({
parsedProtoObj: parsedProtoObjSelector(state),
selectMessage: messageSelector(state),
selectService: serviceSelector(state),
selectUrl: urlSelector(state),
selectRequest: requestSelector(state),
selectResponse: responseSelector(state),
proto: protoSelector(state),
popupStatus: popupSelector(state),
changeTheme: themeSelector(state),
selectWsCommand: setWsSelector(state)
}),
{
setMessageAction: setMessageActionCreator,
setServiceAction: setServiceActionCreator,
setUrlAction: setUrlActionCreator,
setRequestAction: setRequestActionCreator,
sendUnaryRequestAction: sendUnaryRequestActionCreator,
clearResponseEditorAction: clearResponseEditorActionCreator,
togglePopup: showPopupActionCreator,
changeThemeAction: changeThemeActionCreator,
setWsCommandAction: setWsCommandActionCreator,
startWebsocketAction: startWebsocketActionCreator
}
)(Body);
================================================
FILE: src/containers/Footer.tsx
================================================
import React, {FunctionComponent } from 'react'
import { connect } from 'react-redux'
import { Link, RouteComponentProps, withRouter } from 'react-router-dom'
// sets type for props
interface FooterProps {
}
export const Footer: FunctionComponent<FooterProps> = props => {
{
// const {
// incrementAction
// } = props
return (
<div id = "footer">
Built with 💛 by the Yodelays (v1.0 beta)
</div>
)
}
}
export default Footer
// gives the app component access to state and actions from the store
// export default connect(
// //using selector
// (state: RootState) => ({
// test: countSelector(state)
// })
// ,
// {
// incrementAction: incrementActionCreator,
// }
// )(App)
================================================
FILE: src/containers/Header.tsx
================================================
import React, { FunctionComponent } from "react";
import { connect } from "react-redux";
import { Link, RouteComponentProps, withRouter } from "react-router-dom";
import { Button } from "../components/common/Button";
import { RootState } from "../reducers";
import { themeSelector } from "../reducers/changeTheme";
import { changeThemeActionCreator } from "../actions";
// sets type for props
interface HeaderProps {
changeThemeAction: typeof changeThemeActionCreator;
changeTheme: string;
}
export const Header: FunctionComponent<HeaderProps> = props => {
{
const { changeTheme } = props;
// //should evaluate to button-dark-yellow
let toggleThemeName = `header-button-${changeTheme}`;
const navToGithub = () => {
window.location.href = "https://github.com/oslabs-beta/Yodelay";
};
const navToTwitter = () => {
window.location.href = "https://twitter.com/yodelay_io";
};
return (
<div>
{/* On click, sends user back to home page */}
<div style={{ display: "block", height: "15px" }}></div>
<div id="header-container">
<Link to="/">
<Button className={toggleThemeName} text="Yodelay.io"></Button>
</Link>
<div id="social-media">
<Button className="github-button" onClick={navToGithub}></Button>
<Button className="twitter-button" onClick={navToTwitter}></Button>
<div style={{ display: "block", width: "15px" }}></div>
</div>
</div>
</div>
);
}
};
export default connect(
(state: RootState) => ({
changeTheme: themeSelector(state)
}),
{
changeThemeAction: changeThemeActionCreator
}
)(Header);
================================================
FILE: src/containers/Navbar.tsx
================================================
import React, { FunctionComponent, RefObject, useRef, createRef } from "react";
import { connect } from "react-redux";
import { Link, Route } from "react-router-dom";
import { Button } from "../components/common/Button";
import { uploadProtoActionCreator } from "../actions";
import { protoSelector } from "../reducers/uploadProto";
import { RootState } from "../reducers";
import { themeSelector } from "../reducers/changeTheme";
// sets type for props
interface NavbarProps {
inputOpenFileRef?: RefObject<HTMLInputElement>;
showOpenFileDlg?: () => any;
uploadProto?: typeof uploadProtoActionCreator;
protoFile?: File;
changeTheme: string;
}
//Upon user clicking upload proto button in navbar, folder dialog window opens. User-selected file is read and its contents are passed as a payload
export const Navbar: FunctionComponent<NavbarProps> = props => {
{
const { uploadProto, changeTheme } = props;
//Refs allow us to access DOM nodes or React elements created in the render method
//Both createRef and useRef hook returns the same result. createRef returns a new ref on every render while useRef will return the same ref obj each time
//
const inputOpenFileRef = useRef<HTMLInputElement>();
const showOpenFileDlg = () => {
inputOpenFileRef.current.click();
};
const onFileSubmit = () => {
const protoFile = inputOpenFileRef.current.files[0];
const reader = new FileReader();
reader.onloadend = e => {
uploadProto(e.target.result);
};
reader.readAsText(protoFile);
};
let toggleThemeUploadProto =
changeTheme === "dark-yellow" ||
changeTheme === "dark-green" ||
changeTheme === "dark-blue"
? "upload-proto-button-grey"
: "upload-proto-button-light";
let toggleThemeSettings =
changeTheme === "dark-yellow" ||
changeTheme === "dark-green" ||
changeTheme === "dark-blue"
? "settings-button-grey"
: "settings-button-light";
return (
<div>
<Link to="/">
<Button className="home-button"></Button>
</Link>
{/* Upload Proto Button */}
<input
ref={inputOpenFileRef}
type="file"
style={{ display: "none" }}
onChange={onFileSubmit}
/>
<Button
className={toggleThemeUploadProto}
onClick={showOpenFileDlg}
></Button>
<Link to="/settings">
<Button className={toggleThemeSettings}></Button>
</Link>
</div>
);
}
};
export default connect(
// gives the navbar component access to specific state and actions from the store
(state: RootState) => ({
protoContents: protoSelector(state),
changeTheme: themeSelector(state)
}),
{
uploadProto: uploadProtoActionCreator
}
)(Navbar);
================================================
FILE: src/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Yodelay</title>
</head>
<body>
<div id='root'>
</div>
</body>
</html>
================================================
FILE: src/index.tsx
================================================
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import saga from "./sagas/sagas";
import createSagaMiddleware from "redux-saga";
import App from "./containers/App";
import rootReducer from "./reducers";
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(saga);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
================================================
FILE: src/reducers/changeTheme.ts
================================================
import { changeThemeAction, CHANGE_THEME } from "../actions";
import { setIn } from "timm";
import { RootState } from ".";
export interface initialThemeStateType {
theme: string;
}
const initialState: initialThemeStateType = {
theme: "dark-yellow"
//payload will change based on theme: dark-yellow | light-yellow | dark-green | dark-blue
};
export const changeTheme: (
state: initialThemeStateType,
action: changeThemeAction
) => initialThemeStateType = (state = initialState, action) => {
// console.log(action.type)
switch (action.type) {
case CHANGE_THEME: {
//setIn takes in param1) state object, param2) the key in state that is what we want to update, and param3) the value we want to change
return setIn(state, ["theme"], action.payload);
}
}
return state;
};
export const themeSelector: (state: RootState) => string = state =>
state.changeTheme.theme;
================================================
FILE: src/reducers/index.ts
================================================
import { combineReducers } from "redux";
// needs clarifying -- how does it recognizes increment and why does it need an alias
import { test, testState } from "./test";
import { uploadProto, initialProtoStateType } from "./uploadProto";
import { updateMenu, initialMenuStateType } from "./updateMenu";
import { changeTheme, initialThemeStateType } from "./changeTheme";
export interface RootState {
test: testState;
uploadProto: initialProtoStateType;
updateMenu: initialMenuStateType;
changeTheme: initialThemeStateType;
}
// Turns an object whose values are different reducing functions into a single reducing function you can pass to createStore
const rootReducer = combineReducers({
test,
uploadProto,
updateMenu,
changeTheme
});
export default rootReducer;
================================================
FILE: src/reducers/test.ts
================================================
import { INCREMENT, incrementActions, DECREMENT } from '../actions'
import {updateIn, setIn} from 'timm'
import { RootState } from '.'
export interface testState {
count: number
}
const initialState: testState = {
count: 0
}
//this reducer can only take in actions of type increment actions
export const test: (state: testState, action: incrementActions) => testState = (
state = initialState, action) => {
switch (action.type) {
case INCREMENT:{
//setIn takes in param1) state object, param2) the key in state that is what we want to update, and param3) the value we want to change
return setIn(state, ["count"], state.count+1)
// {
// count: state.count + action.payload
// }
}
case DECREMENT: {
//updateIn -- the only thing that changes is that param3 is a function
return updateIn(state, ["count"], (prevCount) => prevCount-1)
}
}
return state
}
// selectors
export const countSelector: (state: RootState)=> number = (state) => state.test.count
//// OLD
// function incrementReducer (
// state = initialState,
// action: incrementActionType
// ): incrementState {
// switch (action.type) {
// case INCREMENT:
// return {
// count: state.count + action.payload
// }
// }
// }
================================================
FILE: src/reducers/updateMenu.ts
================================================
import { setIn } from 'timm'
import { RootState } from '.'
import { loadMenuAction, LOAD_SERVICE_OPTIONS, LOAD_REQUEST_OPTIONS } from '../actions'
export interface initialMenuStateType {
serviceOptions: object;
requestOptions: object
}
const initialState: initialMenuStateType = {
serviceOptions: {},
requestOptions: {}
}
export const updateMenu: (state: initialMenuStateType, action: loadMenuAction) => initialMenuStateType = (state = initialState, action) =>{
switch(action.type){
case LOAD_SERVICE_OPTIONS:{
return setIn(state, ["serviceOptions"], action.payload)
}
case LOAD_REQUEST_OPTIONS:{
return setIn(state, ["requestOptions"], action.payload)
}
}
return state
}
export const serviceMenuSelector: (state: RootState) => object = (state) => state.updateMenu.serviceOptions
export const requestMenuSelector: (state: RootState) => object = (state) => state.updateMenu.requestOptions
================================================
FILE: src/reducers/uploadProto.ts
================================================
import {
uploadProtoAction,
UPLOAD_PROTO,
SET_MESSAGE,
UPLOAD_PROTO_SUCCESSFUL,
SEND_PROTO,
SET_SERVICE,
SET_URL,
SET_REQUEST,
SEND_UNARY_REQUEST,
DISPLAY_UNARY_RESPONSE,
CLEAR_RESPONSE_EDITOR,
SHOW_POPUP,
SET_WS_COMMAND,
} from "../actions";
import { setIn } from "timm";
import { RootState } from ".";
export interface typeResponse {
message: string; //change to [], could impact editor
responseTime?: number;
}
export interface typeRequest {
methodName: string;
streamType: string;
}
export interface initialProtoStateType {
//obj containing pased proto file, incl. services, request methods, etc.
parsedProtosObj: object;
//from here onwards, we capture the user's selections
urlInput: string;
serviceInput: string;
// requestInput: string;
requestInput: typeRequest;
//request message
messageInput: string;
//response obj
response: typeResponse;
//array of response stream
responseStream: typeResponse[];
//ignore this
proto: string | ArrayBuffer;
showPopup: boolean;
wsCommand: string;
}
const initialState: initialProtoStateType = {
parsedProtosObj: {},
urlInput: "",
serviceInput: "",
// requestInput: "",
requestInput: {
methodName: "",
streamType: ""
},
messageInput: "Input message here...",
response: {
message: "View response here!",
responseTime: undefined
},
responseStream: [],
proto: "",
showPopup: false,
wsCommand: ''
};
//uploadProto is a function that takes in state and action as params; it returns an updated state object of type initialProtoStateType
//state type is initialProtoStateType; action type is uploadProtoAction
export const uploadProto: (
state: initialProtoStateType,
action: uploadProtoAction
) => initialProtoStateType = (state = initialState, action) => {
// console.log(action.type)
switch (action.type) {
case UPLOAD_PROTO: {
//setIn takes in param1) state object, param2) the key in state that is what we want to update, and param3) the value we want to change
return setIn(state, ["proto"], action.payload);
}
case SET_MESSAGE: {
return { ...state, messageInput: action.payload };
}
case SET_SERVICE: {
return { ...state, serviceInput: action.payload };
}
case SET_URL: {
return { ...state, urlInput: action.payload };
}
case SET_REQUEST: {
return setIn(state, ["requestInput"], action.payload);
}
case UPLOAD_PROTO_SUCCESSFUL: {
//need to add in functionality to push multiple protoobj to state
return setIn(state, ["parsedProtosObj"], action.payload);
}
case SEND_UNARY_REQUEST: {
return { ...state, responseStream: [...state.responseStream, action.payload] };
}
case DISPLAY_UNARY_RESPONSE: {
return {
...state,
responseStream: [...state.responseStream, action.payload]
};
}
case CLEAR_RESPONSE_EDITOR: {
return { ...state, responseStream: action.payload };
}
case SHOW_POPUP: {
// return setIn(state, ['showPopup'], action.payload);
return { ...state, showPopup: action.payload };
}
case SET_WS_COMMAND: {
return { ...state, wsCommand: action.payload };
}
}
return state;
};
//makes the proto state and parsedProtosObj state available to connected components
export const protoSelector: (
state: RootState
) => string | ArrayBuffer = state => state.uploadProto.proto;
export const messageSelector: (state: RootState) => string = state =>
state.uploadProto.messageInput;
export const serviceSelector: (state: RootState) => string = state =>
state.uploadProto.serviceInput;
export const urlSelector: (state: RootState) => string = state =>
state.uploadProto.urlInput;
export const requestSelector: (state: RootState) => object = state =>
state.uploadProto.requestInput;
export const parsedProtoObjSelector: (state: RootState) => object = state =>
state.uploadProto.parsedProtosObj;
export const responseSelector: (state: RootState) => object = state =>
state.uploadProto.responseStream;
export const popupSelector: (state: RootState) => boolean = state =>
state.uploadProto.showPopup;
export const setWsSelector: (state: RootState) => string = state =>
state.uploadProto.wsCommand;
// selecting all of state for the request saga
export const stateSelector: (state: RootState) => object = state =>
state.uploadProto;
================================================
FILE: src/sagas/sagas.ts
================================================
import { call, put, takeLatest, select, take, fork } from "redux-saga/effects";
import { takeEvery, eventChannel } from "redux-saga";
// import moment from 'moment'
import {
UPLOAD_PROTO,
uploadProto,
uploadProtoSuccessfulActionCreator,
uploadProtoFailedActionCreator,
loadServiceActionCreator,
SEND_UNARY_REQUEST,
sendUnaryRequest,
setMessageActionCreator,
displayUnaryResponseActionCreator,
START_WEBSOCKET
} from "../actions/index";
import { json } from "body-parser";
import { stateSelector } from "../reducers/uploadProto";
//Used payload from uploadProto action as an input for the sendProto saga middleware, enabling us to POST the file string to the express server
// @ts-ignore
const API_PORT = env.API_PORT;
// @ts-ignore
const API_HOST = env.API_HOST;
// @ts-ignore
const API_PROTOCOL = env.API_PROTOCOL;
// @ts-ignore
const NODE_ENV = env.NODE_ENV;
console.log(API_PORT, API_HOST, API_PROTOCOL, NODE_ENV);
function* sendProto({ payload }: uploadProto) {
try {
const jsonProtoFile = JSON.stringify(payload);
const data = yield fetch(
`${API_PROTOCOL}://${API_HOST}:${API_PORT}/upload`,
{
method: "POST",
headers: {
Accept: "text/plain",
"Content-Type": "text/plain"
},
body: jsonProtoFile
}
);
const response = yield data.json();
//No need to connect() to store; yield put apparently does it for us; that's how we could access the uploadProtoSuccessful action creator -- CHECK
yield put(uploadProtoSuccessfulActionCreator(response));
yield put(loadServiceActionCreator(response.services));
} catch ({ error, status }) {
const errorMessage = "error in upload saga";
yield put(uploadProtoFailedActionCreator(errorMessage));
}
}
function* startWebsocket() {
const socket = yield call(connect);
yield fork(read, socket);
yield fork(write, socket);
console.log(socket);
}
function* connect() {
const mySocket = yield new WebSocket(
`ws://${API_HOST}:${API_PORT}/websocket`,
"protocol"
);
return mySocket;
}
function* subscribe(socket?: any) {
const message = yield take(SEND_UNARY_REQUEST);
const state = yield select(stateSelector);
const jsonRequestObj = JSON.stringify({
url: state.urlInput,
serviceInput: state.serviceInput,
messageInput: state.messageInput,
requestInput: state.requestInput,
package: state.parsedProtosObj.package,
protoFile: state.parsedProtosObj.protoFile
});
return eventChannel(emit => {
socket.onmessage = (message: any) => {
emit(message);
};
return () => {
socket.close();
};
});
}
function* read(socket?: any) {
const channel = yield call(subscribe, socket);
while (true) {
let message = yield take(channel);
// dispatch actions with messages recieved from ws connection with server here
yield put(
displayUnaryResponseActionCreator({
message: message.data,
responseTime: message.timeStamp
})
);
}
}
function* write(socket?: any) {
while (true) {
const message = yield take(SEND_UNARY_REQUEST);
const state = yield select(stateSelector);
const jsonRequestObj = JSON.stringify({
url: state.urlInput,
serviceInput: state.serviceInput,
messageInput: state.messageInput,
requestInput: state.requestInput,
package: state.parsedProtosObj.package,
protoFile: state.parsedProtosObj.protoFile,
wsCommand: state.wsCommand
});
socket.send(jsonRequestObj);
}
}
function* saga() {
console.log("hello saga");
yield takeLatest(UPLOAD_PROTO, sendProto);
yield takeLatest(START_WEBSOCKET, startWebsocket);
// const socket = yield call(connect)
// yield fork(read, socket)
// yield fork(write, socket)
}
export default saga;
================================================
FILE: src/scss/colours.scss
================================================
// dark mode
//yodelay default - yellow - dark- theme
$black-background: #202123;
$grey-navbar: #242527;
$yellow: #f9c132;
$grey-font: #e8e8e9;
$disabled: #969696;
$black-field: #1a1b1c;
//yellow - light mode
$white-background: #fafafa;
$yellow: #f9c132; //navbar & font
$grey-font: #e8e8e9;
$disabled: #969696;
$white-field: #fafafa;
//green - dark theme
$black-background: #202123;
$grey-navbar: #242527;
$green: #50fa7b;
$grey-font: #e8e8e9;
$disabled: #969696;
$black-field: #1a1b1c;
//blue - dark theme
$black-background: #202123;
$grey-navbar: #242527;
$blue: #57b5f9;
$grey-font: #e8e8e9;
$disabled: #969696;
$black-field: #1a1b1c;
//misc - not used yet
$black-container: #1d1e1f;
$Yblack-background: #181e23;
$Ygrey-navbar: #232b33;
$Yblue-font: #a6e0db;
================================================
FILE: src/scss/common.scss
================================================
@import 'colours';
//NOT AFFECTED BY THEME
.home-button {
height: 70px;
width: 70px;
background-image: url("dog_vector.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-bottom: 20px;
}
.upload-proto-button-grey {
height: 50px;
width: 50px;
background-image: url("upload_grey.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-top: 20px;
margin-left: 5px;
}
.upload-proto-button-light {
height: 50px;
width: 50px;
background-image: url("upload_light.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-top: 20px;
margin-left: 5px;
}
.settings-button-grey {
height: 50px;
width: 50px;
background-image: url("settings_grey.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-top: 20px;
margin-left: 5px;
}
.settings-button-light {
height: 50px;
width: 50px;
background-image: url("settings_light.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-top: 20px;
margin-left: 5px;
}
.github-button {
height: 50px;
width: 50px;
background-image: url("github_grey.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-top: 10px;
}
.twitter-button {
height: 50px;
width: 50px;
background-image: url("twitter_grey.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-top: 12px;
}
.send-button {
height: 24px;
width: 24px;
background-image: url("sendreq_grey.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-left: 15px;
}
.push-button {
height: 25px;
width: 25px;
background-image: url("adddata2_grey.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-left: 5px;
}
.pause-button {
height: 24px;
width: 24px;
background-image: url("endstream_grey.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
margin-left: 5px;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
// background: rgba(0, 0, 0, 0.6);
// background-color: '#fff';
// z-index: 99;
padding: 50;
}
.modal-main {
position: fixed;
background: $black-background;
width: 50%;
height: 90%;
top: 50%;
left: 50%;
transform: translate(0%, -40%);
color: $white-field;
overflow-x: auto;
z-index: 99;
padding: 30;
margin: 0 auto;
border-radius: 5;
max-width: 500;
min-height: 300;
}
.popup-on {
display: block;
}
.popup-off {
display: none;
}
//AFFECTED BY THEME
.viewProto-button-dark-yellow:hover {
color: $yellow;
}
.viewProto-button-light-yellow:hover {
color: $yellow;
}
.viewProto-button-dark-green:hover {
color: $green;
}
.viewProto-button-dark-blue:hover {
color: $blue;
}
.button-dark-yellow {
height: 40px;
width: 40px;
background-image: url("theme-dark-yellow.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
}
.button-light-yellow {
height: 40px;
width: 40px;
background-image: url("theme-light-yellow.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
}
.button-dark-green {
height: 40px;
width: 40px;
background-image: url("theme-dark-green.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
}
.button-dark-blue {
height: 40px;
width: 40px;
background-image: url("theme-dark-blue.svg");
background-repeat: no-repeat;
background-color: Transparent;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
}
.button {
background-color: Transparent;
background-repeat: no-repeat;
border: none;
overflow: hidden;
outline: none;
color: $disabled;
font-family: 'Roboto Mono', monospace;
font-weight: 500;
font-size: 14px;
cursor: pointer;
}
//NEEDS TO BE BELOW OTHER BUTTON TYPES
.header-button-dark-yellow {
color: $grey-font;
font-weight: 700;
font-size: 30px;
font-family: 'Poppins', sans-serif;
background-color: Transparent;
background-repeat: no-repeat;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
}
.header-button-dark-yellow:hover {
color: $yellow;
}
.header-button-light-yellow {
color: $disabled;
font-weight: 700;
font-size: 30px;
font-family: 'Poppins', sans-serif;
background-color: Transparent;
background-repeat: no-repeat;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
}
.header-button-light-yellow:hover {
color: $yellow;
}
.header-button-dark-green {
color: $grey-font;
font-weight: 700;
font-size: 30px;
font-family: 'Poppins', sans-serif;
background-color: Transparent;
background-repeat: no-repeat;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
}
.header-button-dark-green:hover {
color: $green;
}
.header-button-dark-blue {
color: $grey-font;
font-weight: 700;
font-size: 30px;
font-family: 'Poppins', sans-serif;
background-color: Transparent;
background-repeat: no-repeat;
border: none;
overflow: hidden;
outline: none;
cursor: pointer;
}
.header-button-dark-blue:hover {
color: $blue;
}
================================================
FILE: src/scss/index.scss
================================================
@import "colours";
@import "common";
@import "input";
@import "layout";
================================================
FILE: src/scss/input.scss
================================================
@import "colours";
.menu-options {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.url-input-dark-yellow {
background-color: $black-field;
color: $grey-font;
}
.url-input-light-yellow {
background-color: $white-field;
color: $grey-font;
}
.url-input-dark-green {
background-color: $black-field;
color: $grey-font;
}
.url-input-dark-blue {
background-color: $black-field;
color: $grey-font;
}
================================================
FILE: src/scss/layout.scss
================================================
@import 'colours';
//NOT AFFECTED BY THEME
#app-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
margin-left: 15px;
}
#menu-and-view-section {
display: flex;
flex-direction: row;
justify-content: space-between;
}
#footer {
font-size: 12px;
display: flex;
align-items: flex-end;
}
#header-container {
display: flex;
flex-direction: row;
justify-content: space-between;
}
#social-media {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
//AFFECTED BY THEME
//DEFAULT - DARK-YELLOW MODE
html,
body,
#root,
#main-view-dark-yellow {
margin: 0;
height: 100%;
width: 100%;
background-color: $black-background;
color: $grey-font;
font-weight: 500;
font-size: 14px;
font-family: 'Roboto Mono', monospace;
display: flex;
flex-direction: row;
}
#navbar-dark-yellow {
background-color: $grey-navbar;
display: flex;
flex-direction: column;
width: 50px;
height: 100%;
padding: 15px;
position: -webkit-sticky;
position: sticky;
top: 0;
}
#testProto-dark-yellow {
color: $yellow;
}
//LIGHT-YELLOW MODE
#main-view-light-yellow {
margin: 0;
height: 100%;
width: 100%;
background-color: $white-background;
color: $disabled;
font-weight: 500;
font-size: 14px;
font-family: 'Roboto Mono', monospace;
display: flex;
flex-direction: row;
}
#navbar-light-yellow {
background-color: $yellow;
display: flex;
flex-direction: column;
width: 50px;
height: 100%;
padding: 15px;
position: -webkit-sticky;
position: sticky;
top: 0;
}
#testProto-light-yellow {
color: $yellow;
}
//DARK-GREEN MODE
#main-view-dark-green {
margin: 0;
height: 100%;
width: 100%;
background-color: $black-background;
color: $grey-font;
font-weight: 500;
font-size: 14px;
font-family: 'Roboto Mono', monospace;
display: flex;
flex-direction: row;
}
#navbar-dark-green {
background-color: $grey-navbar;
display: flex;
flex-direction: column;
width: 50px;
height: 100%;
padding: 15px;
position: -webkit-sticky;
position: sticky;
top: 0;
}
#testProto-dark-green {
color: $green;
}
//DARK-BLUE MODE
#main-view-dark-blue {
margin: 0;
height: 100%;
width: 100%;
background-color: $black-background;
color: $grey-font;
font-weight: 500;
font-size: 14px;
font-family: 'Roboto Mono', monospace;
display: flex;
flex-direction: row;
}
#navbar-dark-blue {
background-color: $grey-navbar;
display: flex;
flex-direction: column;
width: 50px;
height: 100%;
padding: 15px;
position: -webkit-sticky;
position: sticky;
top: 0;
}
#testProto-dark-blue {
color: $blue;
}
================================================
FILE: tsconfig.json
================================================
{
"exclude": ["node_modules", "**/*.spec.ts", "server"],
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"target": "es5",
"lib": ["es5", "es6", "dom"],
"jsx": "react",
"allowJs": true,
"moduleResolution": "node",
"esModuleInterop": true,
"downlevelIteration": true,
"traceResolution": true
}
}
================================================
FILE: webpack.dev.ts
================================================
import * as webpack from "webpack";
import HtmlWebPackPlugin from "html-webpack-plugin";
import { CleanWebpackPlugin } from "clean-webpack-plugin";
const config = (env: any): webpack.Configuration => {
const API_PORT = env ? env.API_PORT : undefined;
const API_HOST = env ? env.API_HOST : undefined;
const API_PROTOCOL = env ? env.API_PROTOCOL : undefined;
const NODE_ENV = env ? env.NODE_ENV : "development";
return {
mode: "development",
entry: "./src/index.tsx",
output: {
filename: "bundle.js",
publicPath: "/"
},
resolve: {
extensions: [".ts", ".tsx", ".jsx", ".js", ".json", ".css", ".scss"]
},
devtool: "source-map",
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: "awesome-typescript-loader"
},
{
test: /\.(s*)css$/i,
use: ["style-loader", "css-loader", "sass-loader"]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
{
loader: "url-loader",
options: {
limit: 8000,
name: "./src/scss/[hash]-[name].[ext]"
}
}
]
}
]
},
// @ts-ignore
devServer: {
port: 3000,
historyApiFallback: true,
open: false,
hot: true
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebPackPlugin({
template: "./src/index.html"
}),
new webpack.DefinePlugin({
"env.API_PORT": API_PORT
? JSON.stringify(API_PORT)
: JSON.stringify("8000"),
"env.API_HOST": API_HOST
? JSON.stringify(API_HOST)
: JSON.stringify("localhost"),
"env.API_PROTOCOL": API_PROTOCOL
? JSON.stringify(API_PROTOCOL)
: JSON.stringify("http"),
"env.NODE_ENV": NODE_ENV
? JSON.stringify(NODE_ENV)
: JSON.stringify("development")
}),
new webpack.HotModuleReplacementPlugin()
]
};
};
export default config;
================================================
FILE: webpack.prod.ts
================================================
import * as path from 'path';
import * as webpack from 'webpack';
import HtmlWebPackPlugin from 'html-webpack-plugin';
import {CleanWebpackPlugin} from 'clean-webpack-plugin';
const config = (env: any): webpack.Configuration => {
const API_PORT = env ? env.API_PORT : undefined;
const API_HOST = env ? env.API_HOST : undefined;
const API_PROTOCOL = env ? env.API_PROTOCOL : undefined;
return {
mode: 'production',
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js',
publicPath: '/'
},
resolve: {
extensions: ['.ts', '.tsx', '.jsx', '.js', '.json', '.css', '.scss']
},
module: {
rules: [
{ test: /\.tsx?$/, loader: 'awesome-typescript-loader' },
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.(sc|c)ss$/i,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
use: { loader: 'file-loader'}
},
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebPackPlugin({
template: './src/index.html'
}),
new webpack.DefinePlugin({
'env.API_PORT': API_PORT
? JSON.stringify(API_PORT)
: JSON.stringify('8000'),
'env.API_HOST': API_HOST
? JSON.stringify(API_HOST)
: JSON.stringify('localhost'),
'env.API_PROTOCOL': API_PROTOCOL
? JSON.stringify(API_PROTOCOL)
: JSON.stringify('http'),
'env.NODE_ENV': JSON.stringify('production')
})
]
};
};
export default config;
gitextract_ayrg5r4x/ ├── .babelrc ├── .gitignore ├── LICENSE.md ├── README.md ├── grpc_server/ │ ├── demo.js │ ├── grpc_server.js │ └── uServerStream.js ├── helloworld.proto ├── index.js ├── package.json ├── protos/ │ ├── demo.proto │ ├── helloworld-copy.proto │ ├── helloworld.proto │ ├── output.proto │ ├── route_guide.proto │ └── uGreet.proto ├── server_client/ │ ├── helper_request_func.js │ └── server_client.js ├── src/ │ ├── actions/ │ │ ├── changeTheme.ts │ │ ├── index.ts │ │ ├── test.ts │ │ ├── updateMenu.ts │ │ └── uploadProto.ts │ ├── components/ │ │ ├── DropdownRequest.tsx │ │ ├── DropdownService.tsx │ │ ├── Editor.tsx │ │ ├── EditorRequest.tsx │ │ ├── EditorResponse.tsx │ │ ├── Popup.tsx │ │ ├── Settings.tsx │ │ ├── TestProto.tsx │ │ └── common/ │ │ ├── Button.tsx │ │ └── DropdownMenu.tsx │ ├── containers/ │ │ ├── App.tsx │ │ ├── Body.tsx │ │ ├── Footer.tsx │ │ ├── Header.tsx │ │ └── Navbar.tsx │ ├── index.html │ ├── index.tsx │ ├── reducers/ │ │ ├── changeTheme.ts │ │ ├── index.ts │ │ ├── test.ts │ │ ├── updateMenu.ts │ │ └── uploadProto.ts │ ├── sagas/ │ │ └── sagas.ts │ └── scss/ │ ├── colours.scss │ ├── common.scss │ ├── index.scss │ ├── input.scss │ └── layout.scss ├── tsconfig.json ├── webpack.dev.ts └── webpack.prod.ts
SYMBOL INDEX (88 symbols across 30 files)
FILE: grpc_server/demo.js
constant PROTO_PATH (line 4) | let PROTO_PATH = __dirname + "/../protos/demo.proto";
function YodelayWorld (line 16) | function YodelayWorld(call, callback) {
function toLowerCase (line 24) | function toLowerCase(call, callback) {
function gRPCPermutations (line 33) | function gRPCPermutations(call, callback) {
function greetManyTimes (line 55) | function greetManyTimes(call, callback) {
function longGreet (line 82) | function longGreet(call, callback) {
function main (line 126) | function main() {
FILE: grpc_server/grpc_server.js
constant PROTO_PATH (line 5) | let PROTO_PATH = __dirname + '/../protos/helloworld.proto';
function sayHello (line 28) | function sayHello(call, callback) {
function main (line 53) | function main() {
FILE: grpc_server/uServerStream.js
constant PROTO_PATH (line 6) | let PROTO_PATH = __dirname + '/../protos/uGreet.proto';
function greetManyTimes (line 23) | function greetManyTimes(call, callback) {
function main (line 41) | function main() {
FILE: server_client/helper_request_func.js
function parseProto (line 8) | async function parseProto(uploadParsedReqBody) {
class GrpcRequestClass (line 99) | class GrpcRequestClass extends EventEmitter {
method constructor (line 100) | constructor(websocket) {
method sendInit (line 115) | sendInit (reqbody) {
FILE: server_client/server_client.js
constant PORT (line 9) | const PORT = process.env.PORT || 443;
constant INSECURE_PORT (line 10) | const INSECURE_PORT = process.env.INSECURE_PORT || 80;
constant MODE (line 11) | const MODE = process.env.MODE || "PRODUCTION";
FILE: src/actions/changeTheme.ts
constant CHANGE_THEME (line 3) | const CHANGE_THEME = "CHANGE_THEME";
type changeTheme (line 6) | interface changeTheme {
type changeThemeAction (line 12) | type changeThemeAction = changeTheme;
FILE: src/actions/test.ts
constant INCREMENT (line 5) | const INCREMENT = 'INCREMENT'
constant DECREMENT (line 6) | const DECREMENT = 'DECREMENT'
type incrementAction (line 12) | interface incrementAction {
type decrementAction (line 17) | interface decrementAction {
type incrementActions (line 23) | type incrementActions = incrementAction | decrementAction
FILE: src/actions/updateMenu.ts
constant LOAD_SERVICE_OPTIONS (line 3) | const LOAD_SERVICE_OPTIONS = 'LOAD_SERVICE_OPTIONS '
constant LOAD_REQUEST_OPTIONS (line 4) | const LOAD_REQUEST_OPTIONS = 'LOAD_REQUEST_OPTIONS '
type loadServiceOptions (line 9) | interface loadServiceOptions {
type loadRequestOptions (line 14) | interface loadRequestOptions {
type loadMenuAction (line 20) | type loadMenuAction = loadServiceOptions | loadRequestOptions
FILE: src/actions/uploadProto.ts
constant UPLOAD_PROTO (line 4) | const UPLOAD_PROTO = "UPLOAD_PROTO";
constant SEND_PROTO (line 5) | const SEND_PROTO = "SEND_PROTO";
constant UPLOAD_PROTO_SUCCESSFUL (line 6) | const UPLOAD_PROTO_SUCCESSFUL = "UPLOAD_PROTO_SUCCESSFUL";
constant UPLOAD_PROTO_FAILED (line 7) | const UPLOAD_PROTO_FAILED = "UPLOAD_PROTO_FAILED";
constant SEND_UNARY_REQUEST (line 8) | const SEND_UNARY_REQUEST = "SEND_UNARY_REQUEST";
constant SET_MESSAGE (line 9) | const SET_MESSAGE = "SET_MESSAGE";
constant SET_SERVICE (line 10) | const SET_SERVICE = "SET_SERVICE";
constant SET_URL (line 11) | const SET_URL = "SET_URL";
constant SET_REQUEST (line 12) | const SET_REQUEST = "SET_REQUEST";
constant DISPLAY_UNARY_RESPONSE (line 13) | const DISPLAY_UNARY_RESPONSE = "DISPLAY_UNARY_RESPONSE";
constant CLEAR_RESPONSE_EDITOR (line 14) | const CLEAR_RESPONSE_EDITOR = "CLEAR_RESPONSE_EDITOR";
constant SHOW_POPUP (line 15) | const SHOW_POPUP = "SHOW_POPUP";
constant SET_WS_COMMAND (line 16) | const SET_WS_COMMAND = "SET_WS_COMMAND";
constant START_WEBSOCKET (line 17) | const START_WEBSOCKET = "START_WEBSOCKET";
type uploadProto (line 21) | interface uploadProto {
type sendProto (line 26) | interface sendProto {
type uploadProtoSuccessful (line 31) | interface uploadProtoSuccessful {
type sendUnaryRequest (line 36) | interface sendUnaryRequest {
type displayUnaryResponse (line 41) | interface displayUnaryResponse {
type setMessage (line 46) | interface setMessage {
type setService (line 51) | interface setService {
type setUrl (line 56) | interface setUrl {
type setRequest (line 61) | interface setRequest {
type uploadProtoFailed (line 66) | interface uploadProtoFailed {
type clearResponseEditor (line 71) | interface clearResponseEditor {
type showPopup (line 76) | interface showPopup {
type setWsCommand (line 81) | interface setWsCommand {
type startWebsocket (line 86) | interface startWebsocket {
type uploadProtoAction (line 92) | type uploadProtoAction =
FILE: src/components/DropdownRequest.tsx
type DropdownRequestProps (line 9) | interface DropdownRequestProps {
FILE: src/components/DropdownService.tsx
type DropdownServiceProps (line 7) | interface DropdownServiceProps {
FILE: src/components/Editor.tsx
type editorProps (line 9) | interface editorProps {
FILE: src/components/EditorRequest.tsx
type RequestProps (line 14) | interface RequestProps {
FILE: src/components/EditorResponse.tsx
type ResponseProps (line 15) | interface ResponseProps {
FILE: src/components/Popup.tsx
type PopupProps (line 5) | interface PopupProps {
FILE: src/components/Settings.tsx
type SettingsProps (line 10) | interface SettingsProps {
FILE: src/components/TestProto.tsx
type TestProtoProps (line 26) | interface TestProtoProps {
FILE: src/components/common/Button.tsx
type ButtonProps (line 4) | interface ButtonProps {
FILE: src/components/common/DropdownMenu.tsx
type DropdownMenuProps (line 2) | interface DropdownMenuProps {
FILE: src/containers/App.tsx
type AppProps (line 32) | interface AppProps {
FILE: src/containers/Body.tsx
type BodyProps (line 36) | interface BodyProps {
FILE: src/containers/Footer.tsx
type FooterProps (line 6) | interface FooterProps {
FILE: src/containers/Header.tsx
type HeaderProps (line 10) | interface HeaderProps {
FILE: src/containers/Navbar.tsx
type NavbarProps (line 11) | interface NavbarProps {
FILE: src/reducers/changeTheme.ts
type initialThemeStateType (line 5) | interface initialThemeStateType {
FILE: src/reducers/index.ts
type RootState (line 8) | interface RootState {
FILE: src/reducers/test.ts
type testState (line 5) | interface testState {
FILE: src/reducers/updateMenu.ts
type initialMenuStateType (line 5) | interface initialMenuStateType {
FILE: src/reducers/uploadProto.ts
type typeResponse (line 19) | interface typeResponse {
type typeRequest (line 24) | interface typeRequest {
type initialProtoStateType (line 31) | interface initialProtoStateType {
FILE: src/sagas/sagas.ts
constant API_PORT (line 21) | const API_PORT = env.API_PORT;
constant API_HOST (line 23) | const API_HOST = env.API_HOST;
constant API_PROTOCOL (line 25) | const API_PROTOCOL = env.API_PROTOCOL;
constant NODE_ENV (line 27) | const NODE_ENV = env.NODE_ENV;
Condensed preview — 54 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (120K chars).
[
{
"path": ".babelrc",
"chars": 0,
"preview": ""
},
{
"path": ".gitignore",
"chars": 320,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n# dependencies\nnode_modules/\n/.pnp"
},
{
"path": "LICENSE.md",
"chars": 1130,
"preview": "MIT License\n\nCopyright (c) 2019 Tammy Tan, Cedric Theofanous, German Rovati, Jamie Highsmith, Davey Yedid\n\nPermission is"
},
{
"path": "README.md",
"chars": 3173,
"preview": "<p align=\"center\">\n <img src=\"./src/assets/logo_gif.gif\" />\n</p>\n\n<p align=\"center\">\n <b>Yodelay.io </b> is a browser-"
},
{
"path": "grpc_server/demo.js",
"chars": 4511,
"preview": "let grpc = require(\"grpc\");\nlet protoLoader = require(\"@grpc/proto-loader\");\n// this is our test grpc server:\nlet PROTO_"
},
{
"path": "grpc_server/grpc_server.js",
"chars": 2028,
"preview": "let grpc = require('grpc');\nlet protoLoader = require('@grpc/proto-loader');\n\n// this is our test grpc server:\nlet PROTO"
},
{
"path": "grpc_server/uServerStream.js",
"chars": 1510,
"preview": "const fs = require(\"fs\");\nconst grpc = require(\"grpc\");\nconst protoLoader = require('@grpc/proto-loader');\n\n\nlet PROTO_P"
},
{
"path": "helloworld.proto",
"chars": 650,
"preview": "syntax = \"proto3\";\n\n// option java_multiple_files = true;\n// option java_package = \"io.grpc.examples.helloworld\";\n// opt"
},
{
"path": "index.js",
"chars": 0,
"preview": ""
},
{
"path": "package.json",
"chars": 3941,
"preview": "{\n \"name\": \"yodelay\",\n \"version\": \"1.0.0\",\n \"description\": \"Yodelay source code.\",\n \"repository\": \"\",\n \"main\": \"ind"
},
{
"path": "protos/demo.proto",
"chars": 1756,
"preview": "syntax = \"proto3\";\npackage demo;\n// This service implements a simple guestbook\nservice itIsDemoTimeYodelay {\n // firs"
},
{
"path": "protos/helloworld-copy.proto",
"chars": 2241,
"preview": "syntax = \"proto3\";\n\n// option java_multiple_files = true;\n// option java_package = \"io.grpc.examples.helloworld\";\n// opt"
},
{
"path": "protos/helloworld.proto",
"chars": 842,
"preview": "syntax = \"proto3\";\n\n// option java_multiple_files = true;\n// option java_package = \"io.grpc.examples.helloworld\";\n// opt"
},
{
"path": "protos/output.proto",
"chars": 1756,
"preview": "syntax = \"proto3\";\npackage demo;\n// This service implements a simple guestbook\nservice itIsDemoTimeYodelay {\n // firs"
},
{
"path": "protos/route_guide.proto",
"chars": 3490,
"preview": "// Copyright 2015 gRPC authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "protos/uGreet.proto",
"chars": 1007,
"preview": "syntax = \"proto3\";\n\npackage greet;\n\n\nservice GreetService {\n\n //unary API\n rpc Greet (GreetRequest) returns (Greet"
},
{
"path": "server_client/helper_request_func.js",
"chars": 10855,
"preview": "const grpc = require(\"grpc\");\nconst protoLoader = require(\"@grpc/proto-loader\");\nconst grpc_promise = require(\"grpc-prom"
},
{
"path": "server_client/server_client.js",
"chars": 2637,
"preview": "const express = require(\"express\");\nconst cookieParser = require(\"cookie-parser\");\nconst bodyParser = require(\"body-pars"
},
{
"path": "src/actions/changeTheme.ts",
"chars": 601,
"preview": "//Define action type\n\nexport const CHANGE_THEME = \"CHANGE_THEME\";\n\n//Define shape of action type\nexport interface change"
},
{
"path": "src/actions/index.ts",
"chars": 271,
"preview": "/****************************************single source of all creators and action types***************************** */\n"
},
{
"path": "src/actions/test.ts",
"chars": 1354,
"preview": "// Consolidate actionTypes with this file. Rename this file from actionCreators to increment.ts (we should mirror increm"
},
{
"path": "src/actions/updateMenu.ts",
"chars": 999,
"preview": "\n//Define action type\nexport const LOAD_SERVICE_OPTIONS = 'LOAD_SERVICE_OPTIONS '\nexport const LOAD_REQUEST_OPTIONS = 'L"
},
{
"path": "src/actions/uploadProto.ts",
"chars": 4925,
"preview": "import { typeResponse } from \"../reducers/uploadProto\";\n\n//Define action type\nexport const UPLOAD_PROTO = \"UPLOAD_PROTO\""
},
{
"path": "src/components/DropdownRequest.tsx",
"chars": 3705,
"preview": "import React, { FunctionComponent } from \"react\";\nimport { setRequestActionCreator, \n setMessageActionCreator, \n clear"
},
{
"path": "src/components/DropdownService.tsx",
"chars": 1438,
"preview": "import React, { FunctionComponent } from 'react';\nimport { setServiceActionCreator, \n startWebsocketActionCreator, \n c"
},
{
"path": "src/components/Editor.tsx",
"chars": 930,
"preview": "import React, { FunctionComponent } from \"react\";\nimport { EditorRequest } from \"./EditorRequest\";\nimport { EditorRespon"
},
{
"path": "src/components/EditorRequest.tsx",
"chars": 1670,
"preview": "import React, { FunctionComponent } from \"react\";\n// import AceEditor, {Command} from 'react-ace'\nimport AceEditor from "
},
{
"path": "src/components/EditorResponse.tsx",
"chars": 2215,
"preview": "import React, { FunctionComponent } from \"react\";\n// import AceEditor, {Command} from 'react-ace'\nimport AceEditor from "
},
{
"path": "src/components/Popup.tsx",
"chars": 797,
"preview": "import React, { FunctionComponent } from 'react';\nimport { Button } from './common/Button';\nimport { showPopupActionCrea"
},
{
"path": "src/components/Settings.tsx",
"chars": 1772,
"preview": "import React, { FunctionComponent } from \"react\";\nimport { connect } from \"react-redux\";\nimport { BrowserRouter as Route"
},
{
"path": "src/components/TestProto.tsx",
"chars": 6837,
"preview": "import React, { FunctionComponent } from 'react';\nimport { connect } from 'react-redux';\nimport { BrowserRouter as Route"
},
{
"path": "src/components/common/Button.tsx",
"chars": 546,
"preview": "import React, { FunctionComponent } from 'react'\nimport { urlencoded } from 'body-parser'\n\ninterface ButtonProps {\n cla"
},
{
"path": "src/components/common/DropdownMenu.tsx",
"chars": 805,
"preview": "import React, { FunctionComponent } from 'react';\ninterface DropdownMenuProps {\n id?: string;\n menuOptions?: object;\n}"
},
{
"path": "src/containers/App.tsx",
"chars": 3441,
"preview": "import React, { FunctionComponent } from \"react\";\nimport { connect } from \"react-redux\";\nimport { RootState } from \"../r"
},
{
"path": "src/containers/Body.tsx",
"chars": 4558,
"preview": "import React, { FunctionComponent, useState } from \"react\";\nimport { connect } from \"react-redux\";\nimport TestProto from"
},
{
"path": "src/containers/Footer.tsx",
"chars": 803,
"preview": "import React, {FunctionComponent } from 'react'\nimport { connect } from 'react-redux'\nimport { Link, RouteComponentProps"
},
{
"path": "src/containers/Header.tsx",
"chars": 1704,
"preview": "import React, { FunctionComponent } from \"react\";\nimport { connect } from \"react-redux\";\nimport { Link, RouteComponentPr"
},
{
"path": "src/containers/Navbar.tsx",
"chars": 2843,
"preview": "import React, { FunctionComponent, RefObject, useRef, createRef } from \"react\";\nimport { connect } from \"react-redux\";\ni"
},
{
"path": "src/index.html",
"chars": 304,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "src/index.tsx",
"chars": 659,
"preview": "import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport { Provider } from \"react-redux\";\nimport { createStor"
},
{
"path": "src/reducers/changeTheme.ts",
"chars": 904,
"preview": "import { changeThemeAction, CHANGE_THEME } from \"../actions\";\nimport { setIn } from \"timm\";\nimport { RootState } from \"."
},
{
"path": "src/reducers/index.ts",
"chars": 781,
"preview": "import { combineReducers } from \"redux\";\n// needs clarifying -- how does it recognizes increment and why does it need an"
},
{
"path": "src/reducers/test.ts",
"chars": 1329,
"preview": "import { INCREMENT, incrementActions, DECREMENT } from '../actions'\nimport {updateIn, setIn} from 'timm'\nimport { RootSt"
},
{
"path": "src/reducers/updateMenu.ts",
"chars": 977,
"preview": "import { setIn } from 'timm'\nimport { RootState } from '.'\nimport { loadMenuAction, LOAD_SERVICE_OPTIONS, LOAD_REQUEST_O"
},
{
"path": "src/reducers/uploadProto.ts",
"chars": 4409,
"preview": "import {\n uploadProtoAction,\n UPLOAD_PROTO,\n SET_MESSAGE,\n UPLOAD_PROTO_SUCCESSFUL,\n SEND_PROTO,\n SET_SERVICE,\n S"
},
{
"path": "src/sagas/sagas.ts",
"chars": 3802,
"preview": "import { call, put, takeLatest, select, take, fork } from \"redux-saga/effects\";\nimport { takeEvery, eventChannel } from "
},
{
"path": "src/scss/colours.scss",
"chars": 768,
"preview": "// dark mode\n\n//yodelay default - yellow - dark- theme\n$black-background: #202123;\n$grey-navbar: #242527;\n$yellow: #f9c1"
},
{
"path": "src/scss/common.scss",
"chars": 6039,
"preview": "@import 'colours';\n\n//NOT AFFECTED BY THEME\n.home-button {\n height: 70px;\n width: 70px;\n background-image: url(\"dog_v"
},
{
"path": "src/scss/index.scss",
"chars": 72,
"preview": "@import \"colours\";\n@import \"common\";\n@import \"input\";\n@import \"layout\";\n"
},
{
"path": "src/scss/input.scss",
"chars": 439,
"preview": "@import \"colours\";\n\n.menu-options {\n display: flex;\n flex-direction: row;\n justify-content: flex-start;\n}\n\n.url-input"
},
{
"path": "src/scss/layout.scss",
"chars": 2672,
"preview": "@import 'colours';\n\n//NOT AFFECTED BY THEME\n#app-container {\n display: flex;\n flex-direction: column;\n width: 100%;\n "
},
{
"path": "tsconfig.json",
"chars": 357,
"preview": "{\n \"exclude\": [\"node_modules\", \"**/*.spec.ts\", \"server\"],\n \"compilerOptions\": {\n \"outDir\": \"./dist/\",\n \"noImplic"
},
{
"path": "webpack.dev.ts",
"chars": 2071,
"preview": "import * as webpack from \"webpack\";\nimport HtmlWebPackPlugin from \"html-webpack-plugin\";\nimport { CleanWebpackPlugin } f"
},
{
"path": "webpack.prod.ts",
"chars": 1741,
"preview": "import * as path from 'path';\nimport * as webpack from 'webpack';\nimport HtmlWebPackPlugin from 'html-webpack-plugin';\ni"
}
]
About this extraction
This page contains the full source code of the oslabs-beta/Yodelay GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 54 files (108.8 KB), approximately 28.3k tokens, and a symbol index with 88 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.