├── postcss.config.js
├── vite.config.js
├── tailwind.config.js
├── src
├── main.jsx
├── index.css
├── App.css
├── App.jsx
├── calculator.client.ts
├── assets
│ └── react.svg
└── calculator.ts
├── .gitignore
├── index.html
├── README.md
├── protos
└── calculator.proto
├── .eslintrc.cjs
├── LICENSE
├── package.json
└── public
└── vite.svg
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.jsx'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/protos/calculator.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package calculator;
4 |
5 | service Calculator {
6 | rpc Add(CalculationRequest) returns (CalculationResponse);
7 | rpc Divide(CalculationRequest) returns (CalculationResponse);
8 | }
9 |
10 | message CalculationRequest {
11 | int64 a = 1;
12 | int64 b = 2;
13 | }
14 |
15 | message CalculationResponse { int64 result = 1; }
16 |
17 | service Admin {
18 | rpc GetRequestCount(GetCountRequest) returns (CounterResponse);
19 | }
20 |
21 | message GetCountRequest {}
22 |
23 | message CounterResponse { uint64 count = 1; }
24 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
7 | line-height: 1.5;
8 | font-weight: 400;
9 |
10 | color-scheme: dark;
11 | color: rgba(255, 255, 255, 0.87);
12 | background-color: #242424;
13 |
14 | font-synthesis: none;
15 | text-rendering: optimizeLegibility;
16 | -webkit-font-smoothing: antialiased;
17 | -moz-osx-font-smoothing: grayscale;
18 | }
19 |
20 | body {
21 | margin: 0;
22 | display: flex;
23 | place-items: center;
24 | min-width: 320px;
25 | min-height: 100vh;
26 | }
27 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react/jsx-no-target-blank': 'off',
16 | 'react-refresh/only-export-components': [
17 | 'warn',
18 | { allowConstantExport: true },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2024 Elliott Minns
2 |
3 | This program is free software: you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU General Public License for more details.
12 |
13 | You should have received a copy of the GNU General Public License
14 | along with this program. If not, see .
15 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-vue-app",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@protobuf-ts/grpcweb-transport": "^2.9.3",
14 | "@protobuf-ts/plugin": "^2.9.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^18.2.55",
20 | "@types/react-dom": "^18.2.19",
21 | "@vitejs/plugin-react": "^4.2.1",
22 | "autoprefixer": "^10.4.17",
23 | "eslint": "^8.56.0",
24 | "eslint-plugin-react": "^7.33.2",
25 | "eslint-plugin-react-hooks": "^4.6.0",
26 | "eslint-plugin-react-refresh": "^0.4.5",
27 | "postcss": "^8.4.35",
28 | "tailwindcss": "^3.4.1",
29 | "vite": "^5.1.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import reactLogo from "./assets/react.svg";
3 | import viteLogo from "/vite.svg";
4 | import "./App.css";
5 | import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport";
6 | import { CalculatorClient } from "./calculator.client.ts";
7 |
8 | function App() {
9 | const [num1, setNum1] = useState(0);
10 | const [num2, setNum2] = useState(0);
11 | const [result, setResult] = useState(0);
12 |
13 | let transport = new GrpcWebFetchTransport({
14 | baseUrl: "http://[::1]:50051",
15 | });
16 |
17 | const client = new CalculatorClient(transport);
18 |
19 | const handleAdd = () => {
20 | client.add({ a: num1, b: num2 }).then((response) => {
21 | console.log(response.response.result);
22 | setResult(parseInt(response.response.result));
23 | });
24 | };
25 |
26 | return (
27 | <>
28 |
29 | gRPCalculator
30 |
31 |
56 |
57 |
Result: {result}
58 |
59 | >
60 | );
61 | }
62 |
63 | export default App;
64 |
--------------------------------------------------------------------------------
/src/calculator.client.ts:
--------------------------------------------------------------------------------
1 | // @generated by protobuf-ts 2.9.3
2 | // @generated from protobuf file "calculator.proto" (package "calculator", syntax proto3)
3 | // tslint:disable
4 | import { Admin } from "./calculator";
5 | import type { CounterResponse } from "./calculator";
6 | import type { GetCountRequest } from "./calculator";
7 | import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
8 | import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
9 | import { Calculator } from "./calculator";
10 | import { stackIntercept } from "@protobuf-ts/runtime-rpc";
11 | import type { CalculationResponse } from "./calculator";
12 | import type { CalculationRequest } from "./calculator";
13 | import type { UnaryCall } from "@protobuf-ts/runtime-rpc";
14 | import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
15 | /**
16 | * @generated from protobuf service calculator.Calculator
17 | */
18 | export interface ICalculatorClient {
19 | /**
20 | * @generated from protobuf rpc: Add(calculator.CalculationRequest) returns (calculator.CalculationResponse);
21 | */
22 | add(input: CalculationRequest, options?: RpcOptions): UnaryCall;
23 | /**
24 | * @generated from protobuf rpc: Divide(calculator.CalculationRequest) returns (calculator.CalculationResponse);
25 | */
26 | divide(input: CalculationRequest, options?: RpcOptions): UnaryCall;
27 | }
28 | /**
29 | * @generated from protobuf service calculator.Calculator
30 | */
31 | export class CalculatorClient implements ICalculatorClient, ServiceInfo {
32 | typeName = Calculator.typeName;
33 | methods = Calculator.methods;
34 | options = Calculator.options;
35 | constructor(private readonly _transport: RpcTransport) {
36 | }
37 | /**
38 | * @generated from protobuf rpc: Add(calculator.CalculationRequest) returns (calculator.CalculationResponse);
39 | */
40 | add(input: CalculationRequest, options?: RpcOptions): UnaryCall {
41 | const method = this.methods[0], opt = this._transport.mergeOptions(options);
42 | return stackIntercept("unary", this._transport, method, opt, input);
43 | }
44 | /**
45 | * @generated from protobuf rpc: Divide(calculator.CalculationRequest) returns (calculator.CalculationResponse);
46 | */
47 | divide(input: CalculationRequest, options?: RpcOptions): UnaryCall {
48 | const method = this.methods[1], opt = this._transport.mergeOptions(options);
49 | return stackIntercept("unary", this._transport, method, opt, input);
50 | }
51 | }
52 | /**
53 | * @generated from protobuf service calculator.Admin
54 | */
55 | export interface IAdminClient {
56 | /**
57 | * @generated from protobuf rpc: GetRequestCount(calculator.GetCountRequest) returns (calculator.CounterResponse);
58 | */
59 | getRequestCount(input: GetCountRequest, options?: RpcOptions): UnaryCall;
60 | }
61 | /**
62 | * @generated from protobuf service calculator.Admin
63 | */
64 | export class AdminClient implements IAdminClient, ServiceInfo {
65 | typeName = Admin.typeName;
66 | methods = Admin.methods;
67 | options = Admin.options;
68 | constructor(private readonly _transport: RpcTransport) {
69 | }
70 | /**
71 | * @generated from protobuf rpc: GetRequestCount(calculator.GetCountRequest) returns (calculator.CounterResponse);
72 | */
73 | getRequestCount(input: GetCountRequest, options?: RpcOptions): UnaryCall {
74 | const method = this.methods[0], opt = this._transport.mergeOptions(options);
75 | return stackIntercept("unary", this._transport, method, opt, input);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/calculator.ts:
--------------------------------------------------------------------------------
1 | // @generated by protobuf-ts 2.9.3
2 | // @generated from protobuf file "calculator.proto" (package "calculator", syntax proto3)
3 | // tslint:disable
4 | import { ServiceType } from "@protobuf-ts/runtime-rpc";
5 | import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
6 | import type { IBinaryWriter } from "@protobuf-ts/runtime";
7 | import { WireType } from "@protobuf-ts/runtime";
8 | import type { BinaryReadOptions } from "@protobuf-ts/runtime";
9 | import type { IBinaryReader } from "@protobuf-ts/runtime";
10 | import { UnknownFieldHandler } from "@protobuf-ts/runtime";
11 | import type { PartialMessage } from "@protobuf-ts/runtime";
12 | import { reflectionMergePartial } from "@protobuf-ts/runtime";
13 | import { MessageType } from "@protobuf-ts/runtime";
14 | /**
15 | * @generated from protobuf message calculator.CalculationRequest
16 | */
17 | export interface CalculationRequest {
18 | /**
19 | * @generated from protobuf field: int64 a = 1;
20 | */
21 | a: bigint;
22 | /**
23 | * @generated from protobuf field: int64 b = 2;
24 | */
25 | b: bigint;
26 | }
27 | /**
28 | * @generated from protobuf message calculator.CalculationResponse
29 | */
30 | export interface CalculationResponse {
31 | /**
32 | * @generated from protobuf field: int64 result = 1;
33 | */
34 | result: bigint;
35 | }
36 | /**
37 | * @generated from protobuf message calculator.GetCountRequest
38 | */
39 | export interface GetCountRequest {
40 | }
41 | /**
42 | * @generated from protobuf message calculator.CounterResponse
43 | */
44 | export interface CounterResponse {
45 | /**
46 | * @generated from protobuf field: uint64 count = 1;
47 | */
48 | count: bigint;
49 | }
50 | // @generated message type with reflection information, may provide speed optimized methods
51 | class CalculationRequest$Type extends MessageType {
52 | constructor() {
53 | super("calculator.CalculationRequest", [
54 | { no: 1, name: "a", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ },
55 | { no: 2, name: "b", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }
56 | ]);
57 | }
58 | create(value?: PartialMessage): CalculationRequest {
59 | const message = globalThis.Object.create((this.messagePrototype!));
60 | message.a = 0n;
61 | message.b = 0n;
62 | if (value !== undefined)
63 | reflectionMergePartial(this, message, value);
64 | return message;
65 | }
66 | internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CalculationRequest): CalculationRequest {
67 | let message = target ?? this.create(), end = reader.pos + length;
68 | while (reader.pos < end) {
69 | let [fieldNo, wireType] = reader.tag();
70 | switch (fieldNo) {
71 | case /* int64 a */ 1:
72 | message.a = reader.int64().toBigInt();
73 | break;
74 | case /* int64 b */ 2:
75 | message.b = reader.int64().toBigInt();
76 | break;
77 | default:
78 | let u = options.readUnknownField;
79 | if (u === "throw")
80 | throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
81 | let d = reader.skip(wireType);
82 | if (u !== false)
83 | (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
84 | }
85 | }
86 | return message;
87 | }
88 | internalBinaryWrite(message: CalculationRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
89 | /* int64 a = 1; */
90 | if (message.a !== 0n)
91 | writer.tag(1, WireType.Varint).int64(message.a);
92 | /* int64 b = 2; */
93 | if (message.b !== 0n)
94 | writer.tag(2, WireType.Varint).int64(message.b);
95 | let u = options.writeUnknownFields;
96 | if (u !== false)
97 | (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
98 | return writer;
99 | }
100 | }
101 | /**
102 | * @generated MessageType for protobuf message calculator.CalculationRequest
103 | */
104 | export const CalculationRequest = new CalculationRequest$Type();
105 | // @generated message type with reflection information, may provide speed optimized methods
106 | class CalculationResponse$Type extends MessageType {
107 | constructor() {
108 | super("calculator.CalculationResponse", [
109 | { no: 1, name: "result", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }
110 | ]);
111 | }
112 | create(value?: PartialMessage): CalculationResponse {
113 | const message = globalThis.Object.create((this.messagePrototype!));
114 | message.result = 0n;
115 | if (value !== undefined)
116 | reflectionMergePartial(this, message, value);
117 | return message;
118 | }
119 | internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CalculationResponse): CalculationResponse {
120 | let message = target ?? this.create(), end = reader.pos + length;
121 | while (reader.pos < end) {
122 | let [fieldNo, wireType] = reader.tag();
123 | switch (fieldNo) {
124 | case /* int64 result */ 1:
125 | message.result = reader.int64().toBigInt();
126 | break;
127 | default:
128 | let u = options.readUnknownField;
129 | if (u === "throw")
130 | throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
131 | let d = reader.skip(wireType);
132 | if (u !== false)
133 | (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
134 | }
135 | }
136 | return message;
137 | }
138 | internalBinaryWrite(message: CalculationResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
139 | /* int64 result = 1; */
140 | if (message.result !== 0n)
141 | writer.tag(1, WireType.Varint).int64(message.result);
142 | let u = options.writeUnknownFields;
143 | if (u !== false)
144 | (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
145 | return writer;
146 | }
147 | }
148 | /**
149 | * @generated MessageType for protobuf message calculator.CalculationResponse
150 | */
151 | export const CalculationResponse = new CalculationResponse$Type();
152 | // @generated message type with reflection information, may provide speed optimized methods
153 | class GetCountRequest$Type extends MessageType {
154 | constructor() {
155 | super("calculator.GetCountRequest", []);
156 | }
157 | create(value?: PartialMessage): GetCountRequest {
158 | const message = globalThis.Object.create((this.messagePrototype!));
159 | if (value !== undefined)
160 | reflectionMergePartial(this, message, value);
161 | return message;
162 | }
163 | internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetCountRequest): GetCountRequest {
164 | return target ?? this.create();
165 | }
166 | internalBinaryWrite(message: GetCountRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
167 | let u = options.writeUnknownFields;
168 | if (u !== false)
169 | (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
170 | return writer;
171 | }
172 | }
173 | /**
174 | * @generated MessageType for protobuf message calculator.GetCountRequest
175 | */
176 | export const GetCountRequest = new GetCountRequest$Type();
177 | // @generated message type with reflection information, may provide speed optimized methods
178 | class CounterResponse$Type extends MessageType {
179 | constructor() {
180 | super("calculator.CounterResponse", [
181 | { no: 1, name: "count", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }
182 | ]);
183 | }
184 | create(value?: PartialMessage): CounterResponse {
185 | const message = globalThis.Object.create((this.messagePrototype!));
186 | message.count = 0n;
187 | if (value !== undefined)
188 | reflectionMergePartial(this, message, value);
189 | return message;
190 | }
191 | internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CounterResponse): CounterResponse {
192 | let message = target ?? this.create(), end = reader.pos + length;
193 | while (reader.pos < end) {
194 | let [fieldNo, wireType] = reader.tag();
195 | switch (fieldNo) {
196 | case /* uint64 count */ 1:
197 | message.count = reader.uint64().toBigInt();
198 | break;
199 | default:
200 | let u = options.readUnknownField;
201 | if (u === "throw")
202 | throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
203 | let d = reader.skip(wireType);
204 | if (u !== false)
205 | (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
206 | }
207 | }
208 | return message;
209 | }
210 | internalBinaryWrite(message: CounterResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
211 | /* uint64 count = 1; */
212 | if (message.count !== 0n)
213 | writer.tag(1, WireType.Varint).uint64(message.count);
214 | let u = options.writeUnknownFields;
215 | if (u !== false)
216 | (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
217 | return writer;
218 | }
219 | }
220 | /**
221 | * @generated MessageType for protobuf message calculator.CounterResponse
222 | */
223 | export const CounterResponse = new CounterResponse$Type();
224 | /**
225 | * @generated ServiceType for protobuf service calculator.Calculator
226 | */
227 | export const Calculator = new ServiceType("calculator.Calculator", [
228 | { name: "Add", options: {}, I: CalculationRequest, O: CalculationResponse },
229 | { name: "Divide", options: {}, I: CalculationRequest, O: CalculationResponse }
230 | ]);
231 | /**
232 | * @generated ServiceType for protobuf service calculator.Admin
233 | */
234 | export const Admin = new ServiceType("calculator.Admin", [
235 | { name: "GetRequestCount", options: {}, I: GetCountRequest, O: CounterResponse }
236 | ]);
237 |
--------------------------------------------------------------------------------