diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d22191f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +./docker-data +./target \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/.envrc b/.envrc index 3550a30..c356818 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,2 @@ +dotenv .env use flake diff --git a/.gitignore b/.gitignore index b52c394..e27d09d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ sytest node_modules /target +docker-data diff --git a/client/package-lock.json b/client/package-lock.json index 6d34995..63ee890 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,14 +10,17 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.5", - "ts-proto": "^1.147.1" + "ts-proto": "^1.147.1", + "uuid": "^9.0.0" }, "devDependencies": { "@tauri-apps/cli": "^1.3.0", "@types/react-dom": "^18.2.4", + "@types/uuid": "^9.0.1", "sass": "^1.62.1", "scss": "^0.2.4", - "vite": "^4.3.4" + "vite": "^4.3.4", + "vite-plugin-sass-dts": "^1.3.5" } }, "node_modules/@babel/runtime": { @@ -703,6 +706,12 @@ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, + "node_modules/@types/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -737,6 +746,15 @@ "node": ">=8" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/case-anything": { "version": "2.1.10", "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", @@ -1060,6 +1078,41 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/protobufjs": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", @@ -1305,6 +1358,14 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.4.tgz", @@ -1352,6 +1413,24 @@ "optional": true } } + }, + "node_modules/vite-plugin-sass-dts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/vite-plugin-sass-dts/-/vite-plugin-sass-dts-1.3.5.tgz", + "integrity": "sha512-yGm5uRTDq1qc17ZMJV0bQRgxNfw2a/Ejqc/T2NOILXmcR1z9zHpqqo1FwoDddyTV87GDQZYldqKZOE4O5bGBOw==", + "dev": true, + "dependencies": { + "postcss-js": "^4.0.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "postcss": "^8", + "prettier": "^2.7", + "sass": "*", + "vite": "^3 || ^4" + } } } } diff --git a/client/package.json b/client/package.json index 08ca254..be13309 100644 --- a/client/package.json +++ b/client/package.json @@ -5,9 +5,11 @@ "devDependencies": { "@tauri-apps/cli": "^1.3.0", "@types/react-dom": "^18.2.4", + "@types/uuid": "^9.0.1", "sass": "^1.62.1", "scss": "^0.2.4", - "vite": "^4.3.4" + "vite": "^4.3.4", + "vite-plugin-sass-dts": "^1.3.5" }, "dependencies": { "@reduxjs/toolkit": "^1.9.5", @@ -15,6 +17,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.5", - "ts-proto": "^1.147.1" + "ts-proto": "^1.147.1", + "uuid": "^9.0.0" } } diff --git a/client/src-tauri/src/main.rs b/client/src-tauri/src/main.rs index e7ccce5..73c0401 100644 --- a/client/src-tauri/src/main.rs +++ b/client/src-tauri/src/main.rs @@ -2,12 +2,11 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use anyhow::Result; +use mraow_common::chat_proto::chat_client::ChatClient; use tauri::async_runtime::{Mutex, TokioHandle}; use tonic::transport::channel::Channel; -use mraow_common::chat_proto::{greeter_client::GreeterClient, HelloRequest}; - -type MyGreeterClient = GreeterClient; +type MyGreeterClient = ChatClient; #[tauri::command] async fn send_message( @@ -16,13 +15,13 @@ async fn send_message( ) -> Result<(), ()> { println!("SHIET {state:?}"); - let mut client = state.lock().await; - client - .say_hello(HelloRequest { - message, - ..Default::default() - }) - .await; + // let mut client = state.lock().await; + /* client + .say_hello(HelloRequest { + message, + ..Default::default() + }) + .await; */ Ok(()) } @@ -31,12 +30,15 @@ async fn send_message( async fn main() -> Result<()> { tauri::async_runtime::set(TokioHandle::current()); - let greeter_client = GreeterClient::connect("http://[::1]:50051").await?; + /* + TODO: Make sure this doesn't necessarily have to connect before starting the app + let greeter_client = ChatClient::connect("http://[::1]:50051").await?; let greeter_client = Mutex::new(greeter_client); println!("Connected :)"); + */ tauri::Builder::default() - .manage(greeter_client) + // .manage(greeter_client) .invoke_handler(tauri::generate_handler![send_message]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/client/src/.gitignore b/client/src/.gitignore new file mode 100644 index 0000000..23d89e0 --- /dev/null +++ b/client/src/.gitignore @@ -0,0 +1 @@ +*.scss.d.ts \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index 982ae61..2ca2e7f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,35 +1,18 @@ -import { invoke } from "@tauri-apps/api/tauri"; import { Provider } from "react-redux"; import { store } from "./store"; import { useState } from "react"; import styles from "./App.module.scss"; import LeftSidebar from "./components/LeftSidebar"; +import CenterPanel from "./components/CenterPanel"; export default function App() { - const [currentMessage, setCurrentMessage] = useState(""); - - const onSubmit = (e) => { - e.preventDefault(); - invoke("send_message", { message: currentMessage }); - setCurrentMessage(""); - }; - return (
-

mraow chat

-
- setCurrentMessage(e.currentTarget.value)} - autoFocus - /> -
+
); diff --git a/client/src/components/CenterPanel.module.scss b/client/src/components/CenterPanel.module.scss new file mode 100644 index 0000000..87d5a5f --- /dev/null +++ b/client/src/components/CenterPanel.module.scss @@ -0,0 +1,20 @@ +.centerPanel { + flex-grow: 1; + + display: flex; + flex-direction: column; +} + +.middlePart { + flex-grow: 1; +} + +.form { + margin-block-end: 0; +} + +.input { + width: 100%; + padding: 18px; + outline: none; +} diff --git a/client/src/components/CenterPanel.tsx b/client/src/components/CenterPanel.tsx index d976e9e..02e8a51 100644 --- a/client/src/components/CenterPanel.tsx +++ b/client/src/components/CenterPanel.tsx @@ -1 +1,52 @@ -export default function CenterPanel() {} +import { invoke } from "@tauri-apps/api/tauri"; +import styles from "./CenterPanel.module.scss"; +import { useState } from "react"; +import { useAppDispatch, useAppSelector } from "../store"; +import { messageSelectors, messageSlice } from "../store/messages"; +import { v4 as uuidv4 } from "uuid"; + +export default function CenterPanel() { + const [currentMessage, setCurrentMessage] = useState(""); + const dispatch = useAppDispatch(); + const allMessages = useAppSelector((state) => + messageSelectors.selectAll(state) + ); + + const onSubmit = (e) => { + e.preventDefault(); + invoke("send_message", { message: currentMessage }); + + const id = uuidv4(); + const time = new Date(); + dispatch( + messageSlice.actions.addMessage({ id, time, content: currentMessage }) + ); + setCurrentMessage(""); + }; + + return ( +
+

mraow chat

+ +
+ {allMessages.map((msg) => ( +
+ {msg.time.toISOString()} + {msg.content} +
+ ))} +
+ +
+ setCurrentMessage(e.currentTarget.value)} + autoFocus + /> +
+
+ ); +} diff --git a/client/src/components/LeftSidebar.module.scss b/client/src/components/LeftSidebar.module.scss index 56c5ed8..92656e6 100644 --- a/client/src/components/LeftSidebar.module.scss +++ b/client/src/components/LeftSidebar.module.scss @@ -1,3 +1,4 @@ .leftSidebar { + width: var(--left-sidebar-width); background-color: var(--left-sidebar-background-color); } diff --git a/client/src/components/MessageContainer.tsx b/client/src/components/MessageContainer.tsx new file mode 100644 index 0000000..9d6d4f3 --- /dev/null +++ b/client/src/components/MessageContainer.tsx @@ -0,0 +1,5 @@ +import { useSelector } from "react-redux"; + +export default function MessageContainer() { + return <>; +} diff --git a/client/src/store.ts b/client/src/store.ts deleted file mode 100644 index c5db1dc..0000000 --- a/client/src/store.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { configureStore } from "@reduxjs/toolkit"; - -export const store = configureStore({ - reducer: {}, -}); - -export type RootState = ReturnType; -export type AppDispatch = typeof store.dispatch; diff --git a/client/src/store/index.ts b/client/src/store/index.ts new file mode 100644 index 0000000..23a19c6 --- /dev/null +++ b/client/src/store/index.ts @@ -0,0 +1,14 @@ +import { configureStore } from "@reduxjs/toolkit"; +import { messageSlice } from "./messages"; +import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; + +export const store = configureStore({ + reducer: { + messages: messageSlice.reducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; +export const useAppDispatch: () => AppDispatch = useDispatch; +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/client/src/store/messages.ts b/client/src/store/messages.ts new file mode 100644 index 0000000..a0c7c25 --- /dev/null +++ b/client/src/store/messages.ts @@ -0,0 +1,24 @@ +import { createEntityAdapter, createSlice } from "@reduxjs/toolkit"; +import { RootState } from "."; + +export type Message = { + id: string; + time: Date; + content: string; +}; + +export const messageAdapter = createEntityAdapter({ + selectId: (item) => item.id, +}); + +export const messageSelectors = messageAdapter.getSelectors( + (state) => state.messages +); + +export const messageSlice = createSlice({ + name: "messages", + initialState: messageAdapter.getInitialState(), + reducers: { + addMessage: messageAdapter.addOne, + }, +}); diff --git a/client/src/variables.scss b/client/src/variables.scss index 961b1d8..0b777eb 100644 --- a/client/src/variables.scss +++ b/client/src/variables.scss @@ -1,4 +1,6 @@ :root { + --left-sidebar-width: 288px; + --main-background-color: #eee; --left-sidebar-background-color: #ddd; diff --git a/client/tsconfig.json b/client/tsconfig.json index cb622d2..b72f565 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,5 +1,6 @@ { - "compilerOptions": { - "jsx": "react-jsx" - } + "compilerOptions": { + "esModuleInterop": true, + "jsx": "react-jsx" + } } diff --git a/client/vite.config.js b/client/vite.config.js index 2edfbe2..1e69324 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -1,4 +1,5 @@ import { defineConfig } from "vite"; +import sassDts from "vite-plugin-sass-dts"; export default defineConfig({ // prevent vite from obscuring rust errors @@ -22,4 +23,6 @@ export default defineConfig({ // produce sourcemaps for debug builds sourcemap: !!process.env.TAURI_DEBUG, }, + + plugins: [sassDts({ global: { generate: true } })], }); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..38bbf37 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3" + +services: + database: + image: cassandra + volumes: + - ./docker-data/cassandra:/var/lib/cassandra + ports: + - "7000:7000" diff --git a/proto/chat.proto b/proto/chat.proto index 6850840..d1dad2d 100644 --- a/proto/chat.proto +++ b/proto/chat.proto @@ -5,43 +5,42 @@ package chat; import "google/protobuf/empty.proto"; message ChatMessage { - string from = 1; - string msg = 2; - string time = 3; + string messageId = 1; + string fromUserId = 2; + string toUserId = 3; + string toRoomId = 4; + string content = 5; } -message Channel {} +message Room { + string name = 1; +} -message UserList { repeated User user = 1; } +message UserList { repeated User users = 1; } -message RoomList {} +message RoomList {repeated Room rooms = 1;} message User { string username = 1; } +// RPC Messages + message JoinResponse {} -service Chat { - rpc join(User) returns (JoinResponse) {} - rpc sendMsg(ChatMessage) returns (google.protobuf.Empty) {} - rpc receiveMsg(google.protobuf.Empty) returns (stream ChatMessage) {} +message RoomAction { + string roomId = 1; + string userId = 2; + string action = 3; +} +service Chat { + // Rooms + rpc roomAction(RoomAction) returns (JoinResponse) {} + + // Messages + rpc sendMsg(ChatMessage) returns (google.protobuf.Empty) {} + rpc receiveMsgs(google.protobuf.Empty) returns (stream ChatMessage) {} + + // List rpc listRooms(google.protobuf.Empty) returns (RoomList) {} rpc getAllUsers(google.protobuf.Empty) returns (UserList) {} -} - -// Hello world shit - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello(HelloRequest) returns (HelloReply) {} -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; - string message = 2; -} - -// The response message containing the greetings -message HelloReply { string message = 1; } +} \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..fec94fb --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1 @@ +FROM rust \ No newline at end of file diff --git a/server/src/main.rs b/server/src/main.rs index 28b889f..2c85e88 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -12,8 +12,7 @@ use tower::{Layer, Service}; use mraow_common::chat_proto::{ chat_server::{Chat, ChatServer}, - ChatMessage, HelloReply, HelloRequest, JoinResponse, RoomList, User, - UserList, + ChatMessage, JoinResponse, RoomList, User, UserList, }; type ResponseStream =