Add real time messaging
This commit is contained in:
parent
0163eab540
commit
9153ba841d
61 changed files with 882 additions and 230 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "pulsar-web",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
|
|
|||
10
src/App.tsx
10
src/App.tsx
|
|
@ -1,15 +1,17 @@
|
|||
import { Route, Router } from "@solidjs/router";
|
||||
import type { Component } from "solid-js";
|
||||
import { MainView } from "./views/MainView";
|
||||
import { SettingsView } from "./views/SettingsView";
|
||||
import { StartView } from "./views/StartView";
|
||||
import { AppView } from "./views/AppView";
|
||||
import { LoginView } from "./views/LoginView";
|
||||
import { RegisterView } from "./views/RegisterView";
|
||||
|
||||
const App: Component = () => {
|
||||
return (
|
||||
<Router>
|
||||
<Route path="/" component={MainView} />
|
||||
<Route path="/settings" component={SettingsView} />
|
||||
<Route path="/" component={StartView} />
|
||||
<Route path="/app" component={AppView} />
|
||||
<Route path="/login" component={LoginView} />
|
||||
<Route path="/register" component={RegisterView} />
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import { callApi, HTTP } from "../tools";
|
|||
import {
|
||||
IFetchLoginRequest,
|
||||
IFetchLoginResponse,
|
||||
IFetchRefreshResponse,
|
||||
IFetchRefreshResponseError,
|
||||
IFetchRefreshResponseSuccess,
|
||||
} from "./types";
|
||||
|
||||
const fetchLoginApi = async (
|
||||
|
|
@ -11,7 +12,9 @@ const fetchLoginApi = async (
|
|||
return await callApi(HTTP.POST, `auth/login`, request, true);
|
||||
};
|
||||
|
||||
const fetchRefreshApi = async (): Promise<IFetchRefreshResponse> => {
|
||||
const fetchRefreshApi = async (): Promise<
|
||||
IFetchRefreshResponseError | IFetchRefreshResponseSuccess
|
||||
> => {
|
||||
return await callApi(HTTP.GET, `auth/refresh`, undefined, true);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ interface IFetchLoginResponse {
|
|||
ownerId: string;
|
||||
}
|
||||
|
||||
interface IFetchRefreshResponse {
|
||||
interface IFetchRefreshResponseError {
|
||||
error: string;
|
||||
}
|
||||
|
||||
interface IFetchRefreshResponseSuccess {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
token: string;
|
||||
|
|
@ -17,5 +21,6 @@ interface IFetchRefreshResponse {
|
|||
export {
|
||||
type IFetchLoginRequest,
|
||||
type IFetchLoginResponse,
|
||||
type IFetchRefreshResponse,
|
||||
type IFetchRefreshResponseError,
|
||||
type IFetchRefreshResponseSuccess,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {
|
|||
IUpdateChannelResponse,
|
||||
IRemoveChannelRequest,
|
||||
IRemoveChannelResponse,
|
||||
IFetchChannelMessagesRequest,
|
||||
IFetchChannelMessagesResponse,
|
||||
} from "./types";
|
||||
|
||||
const fetchChannelApi = async (
|
||||
|
|
@ -34,9 +36,16 @@ const removeChannelApi = async (
|
|||
return await callApi(HTTP.DELETE, `channel/${request.id}`);
|
||||
};
|
||||
|
||||
const fetchChannelMessagesApi = async (
|
||||
request: IFetchChannelMessagesRequest,
|
||||
): Promise<IFetchChannelMessagesResponse> => {
|
||||
return await callApi(HTTP.GET, `channel/${request.id}/messages`);
|
||||
};
|
||||
|
||||
export {
|
||||
fetchChannelApi,
|
||||
createChannelApi,
|
||||
updateChannelApi,
|
||||
removeChannelApi,
|
||||
fetchChannelMessagesApi,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,6 +36,23 @@ interface IRemoveChannelResponse {
|
|||
communityId: string;
|
||||
}
|
||||
|
||||
interface IFetchChannelMessagesRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface IFetchChannelMessagesResponse {
|
||||
id: string;
|
||||
messages: IFetchChannelMessage[];
|
||||
}
|
||||
|
||||
interface IFetchChannelMessage {
|
||||
id: string;
|
||||
text: string;
|
||||
edited: boolean;
|
||||
ownerId: string;
|
||||
creationDate: number;
|
||||
}
|
||||
|
||||
export {
|
||||
type IFetchChannel,
|
||||
type IFetchChannelRequest,
|
||||
|
|
@ -46,4 +63,7 @@ export {
|
|||
type IUpdateChannelResponse,
|
||||
type IRemoveChannelResponse,
|
||||
type IRemoveChannelRequest,
|
||||
type IFetchChannelMessagesRequest,
|
||||
type IFetchChannelMessagesResponse,
|
||||
type IFetchChannelMessage,
|
||||
};
|
||||
|
|
|
|||
2
src/api/message/index.ts
Normal file
2
src/api/message/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./message";
|
||||
export * from "./types";
|
||||
42
src/api/message/message.ts
Normal file
42
src/api/message/message.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { callApi, HTTP } from "../tools";
|
||||
import {
|
||||
IFetchMessageRequest,
|
||||
IFetchMessageResponse,
|
||||
ICreateMessageRequest,
|
||||
ICreateMessageResponse,
|
||||
IUpdateMessageRequest,
|
||||
IUpdateMessageResponse,
|
||||
IRemoveMessageRequest,
|
||||
IRemoveMessageResponse,
|
||||
} from "./types";
|
||||
|
||||
const fetchMessageApi = async (
|
||||
request: IFetchMessageRequest,
|
||||
): Promise<IFetchMessageResponse> => {
|
||||
return await callApi(HTTP.GET, `message/${request.id}`);
|
||||
};
|
||||
|
||||
const createMessageApi = async (
|
||||
request: ICreateMessageRequest,
|
||||
): Promise<ICreateMessageResponse> => {
|
||||
return await callApi(HTTP.POST, `message`, request);
|
||||
};
|
||||
|
||||
const updateMessageApi = async (
|
||||
request: IUpdateMessageRequest,
|
||||
): Promise<IUpdateMessageResponse> => {
|
||||
return await callApi(HTTP.PATCH, `message/${request.id}`, request);
|
||||
};
|
||||
|
||||
const removeMessageApi = async (
|
||||
request: IRemoveMessageRequest,
|
||||
): Promise<IRemoveMessageResponse> => {
|
||||
return await callApi(HTTP.DELETE, `message/${request.id}`);
|
||||
};
|
||||
|
||||
export {
|
||||
fetchMessageApi,
|
||||
createMessageApi,
|
||||
updateMessageApi,
|
||||
removeMessageApi,
|
||||
};
|
||||
51
src/api/message/types.ts
Normal file
51
src/api/message/types.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
interface IFetchMessage {
|
||||
id: string;
|
||||
text: string;
|
||||
editHistory: string[];
|
||||
edited: boolean;
|
||||
ownerId: string;
|
||||
channelId: string;
|
||||
creationDate: number;
|
||||
}
|
||||
|
||||
interface IFetchMessageRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface IFetchMessageResponse extends IFetchMessage {}
|
||||
|
||||
interface ICreateMessageRequest {
|
||||
text: string;
|
||||
channelId: string;
|
||||
}
|
||||
|
||||
interface ICreateMessageResponse extends IFetchMessage {}
|
||||
|
||||
interface IUpdateMessageRequest {
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface IUpdateMessageResponse extends IFetchMessage {}
|
||||
|
||||
interface IRemoveMessageRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface IRemoveMessageResponse {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
channelId: string;
|
||||
}
|
||||
|
||||
export {
|
||||
type IFetchMessage,
|
||||
type IFetchMessageRequest,
|
||||
type IFetchMessageResponse,
|
||||
type ICreateMessageRequest,
|
||||
type ICreateMessageResponse,
|
||||
type IUpdateMessageRequest,
|
||||
type IUpdateMessageResponse,
|
||||
type IRemoveMessageResponse,
|
||||
type IRemoveMessageRequest,
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Component } from "solid-js";
|
||||
import { ICommunityBarProps } from "./types";
|
||||
import { SettingsIcon } from "../icons";
|
||||
|
||||
const CommunityBar: Component<ICommunityBarProps> = (
|
||||
props: ICommunityBarProps,
|
||||
|
|
@ -8,9 +9,17 @@ const CommunityBar: Component<ICommunityBarProps> = (
|
|||
<div
|
||||
class={`absolute w-full top-0 z-10 bg-cover bg-top bg-no-repeat bg-[url('${props.avatar}')]`}
|
||||
>
|
||||
<div class="flex flex-col justify-center bg-stone-800/25 backdrop-blur-md h-16 w-full shadow-bar px-5">
|
||||
<h2 class="text-sm font-bold">{props.name}</h2>
|
||||
<p class="text-xs">{props.description}</p>
|
||||
<div class="flex flex-row justify-between items-center bg-stone-800/25 backdrop-blur-md h-16 shadow-bar pl-5 pr-3">
|
||||
<div class="flex flex-col justify-center">
|
||||
<h2 class="text-sm font-bold">{props.name}</h2>
|
||||
<p class="text-xs">{props.description}</p>
|
||||
</div>
|
||||
<div
|
||||
class="bg-stone-950/25 cursor-pointer rounded-full w-10 h-10 p-2 hover:bg-stone-950/75"
|
||||
onClick={props.onSettingsClick}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ interface ICommunityBarProps {
|
|||
name?: string;
|
||||
description?: string;
|
||||
avatar?: string;
|
||||
onSettingsClick?: () => void;
|
||||
}
|
||||
|
||||
export { type ICommunityBarProps };
|
||||
|
|
|
|||
|
|
@ -5,14 +5,19 @@ import { CommunityActionTypes } from "../../store/community";
|
|||
import { InviteActionTypes } from "../../store/invite";
|
||||
|
||||
const CommunityModal: Component<ICommunityModalProps> = (props) => {
|
||||
const [getCommunityName, setCommunityName] = createSignal<string>("");
|
||||
const [getInviteId, setInviteId] = createSignal<string>("");
|
||||
const [getCommunityName, setCommunityName] = createSignal("");
|
||||
const [getInviteId, setInviteId] = createSignal("");
|
||||
|
||||
const onCreateCommunity = () => {
|
||||
const communityName = getCommunityName();
|
||||
if (!communityName || communityName.trim().length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: CommunityActionTypes.CREATE_COMMUNITY_START,
|
||||
payload: {
|
||||
name: getCommunityName(),
|
||||
name: communityName,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -22,9 +27,14 @@ const CommunityModal: Component<ICommunityModalProps> = (props) => {
|
|||
};
|
||||
|
||||
const onJoinCommunity = () => {
|
||||
const inviteId = getInviteId();
|
||||
if (!inviteId || inviteId.trim().length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: InviteActionTypes.ACCEPT_INVITE_START,
|
||||
payload: getInviteId(),
|
||||
payload: inviteId,
|
||||
});
|
||||
|
||||
setInviteId("");
|
||||
|
|
@ -32,6 +42,12 @@ const CommunityModal: Component<ICommunityModalProps> = (props) => {
|
|||
props.onClose?.();
|
||||
};
|
||||
|
||||
const handleEnter = (e: KeyboardEvent, callback: () => void) => {
|
||||
if (e.key === "Enter") {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const createCommunityHtml = () => (
|
||||
<>
|
||||
<h3 class="text-lg font-bold text-center mb-6">
|
||||
|
|
@ -44,6 +60,7 @@ const CommunityModal: Component<ICommunityModalProps> = (props) => {
|
|||
placeholder="Enter name of the new community"
|
||||
value={getCommunityName()}
|
||||
onInput={(e) => setCommunityName(e.currentTarget.value)}
|
||||
onKeyDown={(e) => handleEnter(e, onCreateCommunity)}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
|
|
@ -68,6 +85,7 @@ const CommunityModal: Component<ICommunityModalProps> = (props) => {
|
|||
placeholder="Enter invite ID"
|
||||
value={getInviteId()}
|
||||
onInput={(e) => setInviteId(e.currentTarget.value)}
|
||||
onKeyDown={(e) => handleEnter(e, onJoinCommunity)}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import type { Component } from "solid-js";
|
||||
import { ICommunitySettingsModalProps } from "./types";
|
||||
|
||||
const CommunitySettingsModal: Component<ICommunitySettingsModalProps> = (
|
||||
props,
|
||||
) => {
|
||||
return (
|
||||
<div>
|
||||
<dialog ref={props.dialogRef} class="modal bg-[#00000050]">
|
||||
<div class="modal-box bg-stone-950 rounded-3xl">
|
||||
<h3 class="text-lg font-bold text-center">
|
||||
Community Settings
|
||||
</h3>
|
||||
<p class="py-4 text-center">Not implemented yet</p>
|
||||
</div>
|
||||
<form
|
||||
onClick={props.onClose}
|
||||
method="dialog"
|
||||
class="modal-backdrop"
|
||||
></form>
|
||||
</dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommunitySettingsModal;
|
||||
2
src/components/CommunitySettingsModal/index.ts
Normal file
2
src/components/CommunitySettingsModal/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./CommunitySettingsModal";
|
||||
export * from "./types";
|
||||
6
src/components/CommunitySettingsModal/types.ts
Normal file
6
src/components/CommunitySettingsModal/types.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
interface ICommunitySettingsModalProps {
|
||||
dialogRef?: (element: HTMLDialogElement) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export { type ICommunitySettingsModalProps };
|
||||
|
|
@ -1,13 +1,31 @@
|
|||
import type { Component } from "solid-js";
|
||||
import { IMessageBarProps } from "./types";
|
||||
|
||||
const MessageBar: Component<IMessageBarProps> = (props: IMessageBarProps) => {
|
||||
const handleEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
props.onSend?.();
|
||||
}
|
||||
};
|
||||
|
||||
const MessageBar: Component = () => {
|
||||
return (
|
||||
<div class="absolute w-full bottom-0 p-4 z-10">
|
||||
<div class="bg-stone-800/25 backdrop-blur-lg h-16 shadow-bar p-2 flex flex-row gap-2 rounded-full">
|
||||
<label class="bg-stone-800/50 backdrop-blur-lg input w-full h-full rounded-full focus:border-none outline-none">
|
||||
<input type="text" placeholder="Send a message..." />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Send a message..."
|
||||
value={props.text}
|
||||
onInput={(e) =>
|
||||
props.onChangeText?.(e.currentTarget.value)
|
||||
}
|
||||
onKeyDown={handleEnter}
|
||||
/>
|
||||
</label>
|
||||
<button class="bg-stone-950/50 backdrop-blur-lg btn btn-neutral h-full rounded-full">
|
||||
<button
|
||||
class="bg-stone-950/50 backdrop-blur-lg btn btn-neutral h-full rounded-full"
|
||||
onClick={props.onSend}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export * from "./MessageBar";
|
||||
export * from "./types";
|
||||
|
|
|
|||
7
src/components/MessageBar/types.ts
Normal file
7
src/components/MessageBar/types.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
interface IMessageBarProps {
|
||||
text: string;
|
||||
onChangeText?: (text: string) => void;
|
||||
onSend?: () => void;
|
||||
}
|
||||
|
||||
export { type IMessageBarProps };
|
||||
|
|
@ -12,6 +12,10 @@ const fetchLogin = async (username: string, password: string) => {
|
|||
type: AuthActionTypes.FETCH_LOGIN_FINISH,
|
||||
payload: data,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: AuthActionTypes.FETCH_REFRESH_START,
|
||||
});
|
||||
};
|
||||
|
||||
const fetchRefresh = async () => {
|
||||
|
|
@ -21,6 +25,11 @@ const fetchRefresh = async () => {
|
|||
type: AuthActionTypes.FETCH_REFRESH_FINISH,
|
||||
payload: data,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: AuthActionTypes.SET_LOGGED_IN,
|
||||
payload: "id" in data,
|
||||
});
|
||||
};
|
||||
|
||||
export { fetchLogin, fetchRefresh };
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
createChannelApi,
|
||||
updateChannelApi,
|
||||
removeChannelApi,
|
||||
fetchChannelMessagesApi,
|
||||
} from "../../api/channel";
|
||||
import { ChannelActionTypes } from "../../store/channel";
|
||||
import { dispatch } from "../../store/state";
|
||||
|
|
@ -58,4 +59,21 @@ const removeChannel = async (id: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
export { fetchChannel, createChannel, updateChannel, removeChannel };
|
||||
const fetchChannelMessages = async (id: string) => {
|
||||
const data = await fetchChannelMessagesApi({
|
||||
id: id,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ChannelActionTypes.FETCH_CHANNEL_MESSAGES_FINISH,
|
||||
payload: data,
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
fetchChannel,
|
||||
createChannel,
|
||||
updateChannel,
|
||||
removeChannel,
|
||||
fetchChannelMessages,
|
||||
};
|
||||
|
|
|
|||
1
src/services/message/index.ts
Normal file
1
src/services/message/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./message";
|
||||
56
src/services/message/message.ts
Normal file
56
src/services/message/message.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import {
|
||||
fetchMessageApi,
|
||||
createMessageApi,
|
||||
updateMessageApi,
|
||||
removeMessageApi,
|
||||
} from "../../api/message";
|
||||
import { MessageActionTypes } from "../../store/message";
|
||||
import { dispatch } from "../../store/state";
|
||||
|
||||
const fetchMessage = async (id: string) => {
|
||||
const data = await fetchMessageApi({
|
||||
id: id,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: MessageActionTypes.FETCH_MESSAGE_FINISH,
|
||||
payload: data,
|
||||
});
|
||||
};
|
||||
|
||||
const createMessage = async (text: string, channelId: string) => {
|
||||
const data = await createMessageApi({
|
||||
text: text,
|
||||
channelId: channelId,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: MessageActionTypes.CREATE_MESSAGE_FINISH,
|
||||
payload: data,
|
||||
});
|
||||
};
|
||||
|
||||
const updateMessage = async (id: string, text: string) => {
|
||||
const data = await updateMessageApi({
|
||||
id: id,
|
||||
text: text,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: MessageActionTypes.UPDATE_MESSAGE_FINISH,
|
||||
payload: data,
|
||||
});
|
||||
};
|
||||
|
||||
const removeMessage = async (id: string) => {
|
||||
const data = await removeMessageApi({
|
||||
id: id,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: MessageActionTypes.REMOVE_MESSAGE_FINISH,
|
||||
payload: data,
|
||||
});
|
||||
};
|
||||
|
||||
export { fetchMessage, createMessage, updateMessage, removeMessage };
|
||||
6
src/services/websocket/config.json
Normal file
6
src/services/websocket/config.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"schema": "ws",
|
||||
"url": "localhost",
|
||||
"port": 3012,
|
||||
"path": "ws"
|
||||
}
|
||||
2
src/services/websocket/index.ts
Normal file
2
src/services/websocket/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./websocket";
|
||||
export * from "./types";
|
||||
46
src/services/websocket/types.ts
Normal file
46
src/services/websocket/types.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { IFetchChannelMessage } from "../../api/channel";
|
||||
|
||||
enum SocketRequestTypes {
|
||||
PING = "PING",
|
||||
}
|
||||
|
||||
enum SocketMessageTypes {
|
||||
NEW_ANNOUNCEMENT = "NEW_ANNOUNCEMENT",
|
||||
NEW_MESSAGE = "NEW_MESSAGE",
|
||||
NEW_CHANNEL = "NEW_CHANNEL",
|
||||
}
|
||||
|
||||
type SocketRequest = {
|
||||
type: SocketRequestTypes.PING;
|
||||
};
|
||||
|
||||
type SocketMessage =
|
||||
| {
|
||||
type: SocketMessageTypes.NEW_ANNOUNCEMENT;
|
||||
payload: {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: SocketMessageTypes.NEW_MESSAGE;
|
||||
payload: {
|
||||
channelId: string;
|
||||
message: IFetchChannelMessage;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: SocketMessageTypes.NEW_CHANNEL;
|
||||
payload: {
|
||||
id: string;
|
||||
communityId: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
SocketRequestTypes,
|
||||
SocketMessageTypes,
|
||||
type SocketRequest,
|
||||
type SocketMessage,
|
||||
};
|
||||
57
src/services/websocket/websocket.ts
Normal file
57
src/services/websocket/websocket.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { ChannelActionTypes } from "../../store/channel";
|
||||
import { dispatch } from "../../store/state";
|
||||
import config from "./config.json";
|
||||
import { SocketMessage, SocketMessageTypes } from "./types";
|
||||
|
||||
let connection: WebSocket;
|
||||
|
||||
const connectWs = () => {
|
||||
connection = new WebSocket(
|
||||
`${config.schema}://${config.url}:${config.port}/${config.path}`,
|
||||
);
|
||||
|
||||
connection.onclose = onCloseWs;
|
||||
connection.onmessage = onMessageWs;
|
||||
};
|
||||
|
||||
const onCloseWs = () => {
|
||||
setTimeout(connectWs, 10000);
|
||||
};
|
||||
|
||||
const onMessageWs = (event: MessageEvent) => {
|
||||
const message = parseMessage(event.data);
|
||||
|
||||
if (message) {
|
||||
handleMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
const parseMessage = (data: string): SocketMessage | null => {
|
||||
const request = JSON.parse(data) as SocketMessage;
|
||||
|
||||
if (!request.type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
const handleMessage = (message: SocketMessage) => {
|
||||
switch (message.type) {
|
||||
case SocketMessageTypes.NEW_MESSAGE: {
|
||||
dispatch({
|
||||
type: ChannelActionTypes.SET_CHANNEL_MESSAGE,
|
||||
payload: {
|
||||
id: message.payload.channelId,
|
||||
message: message.payload.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
case SocketMessageTypes.NEW_CHANNEL: {
|
||||
}
|
||||
case SocketMessageTypes.NEW_ANNOUNCEMENT: {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { connection, connectWs };
|
||||
|
|
@ -6,6 +6,7 @@ import { ChannelActionTypes, ChannelAction } from "./channel";
|
|||
import { RoleActionTypes, RoleAction } from "./role";
|
||||
import { SessionActionTypes, SessionAction } from "./session";
|
||||
import { InviteActionTypes, InviteAction } from "./invite";
|
||||
import { MessageActionTypes, MessageAction } from "./message";
|
||||
|
||||
type ActionTypes =
|
||||
| AppActionTypes
|
||||
|
|
@ -15,7 +16,8 @@ type ActionTypes =
|
|||
| ChannelActionTypes
|
||||
| RoleActionTypes
|
||||
| SessionActionTypes
|
||||
| InviteActionTypes;
|
||||
| InviteActionTypes
|
||||
| MessageActionTypes;
|
||||
|
||||
type Action =
|
||||
| AppAction
|
||||
|
|
@ -25,6 +27,7 @@ type Action =
|
|||
| ChannelAction
|
||||
| RoleAction
|
||||
| SessionAction
|
||||
| InviteAction;
|
||||
| InviteAction
|
||||
| MessageAction;
|
||||
|
||||
export { type Action, type ActionTypes };
|
||||
|
|
|
|||
|
|
@ -2,11 +2,16 @@ enum AppActionTypes {
|
|||
SET_HOME_OPEN = "SET_HOME_OPEN",
|
||||
SET_SETTINGS_OPEN = "SET_SETTINGS_OPEN",
|
||||
SET_ADD_COMMUNITY_OPEN = "SET_ADD_COMMUNITY_OPEN",
|
||||
SET_ADD_COMMUNITY_SETTINGS_OPEN = "SET_ADD_COMMUNITY_SETTINGS_OPEN",
|
||||
}
|
||||
|
||||
type AppAction =
|
||||
| { type: AppActionTypes.SET_HOME_OPEN; payload: boolean }
|
||||
| { type: AppActionTypes.SET_SETTINGS_OPEN; payload: boolean }
|
||||
| { type: AppActionTypes.SET_ADD_COMMUNITY_OPEN; payload: boolean };
|
||||
| { type: AppActionTypes.SET_ADD_COMMUNITY_OPEN; payload: boolean }
|
||||
| {
|
||||
type: AppActionTypes.SET_ADD_COMMUNITY_SETTINGS_OPEN;
|
||||
payload: boolean;
|
||||
};
|
||||
|
||||
export { AppActionTypes, type AppAction };
|
||||
|
|
|
|||
|
|
@ -15,6 +15,15 @@ function appReducer(_state: IAppState, action: AppAction) {
|
|||
setState("app", "dialogsOpen", "addCommunityOpen", false);
|
||||
setState("app", "dialogsOpen", "addCommunityOpen", action.payload);
|
||||
break;
|
||||
case AppActionTypes.SET_ADD_COMMUNITY_SETTINGS_OPEN:
|
||||
setState("app", "dialogsOpen", "communitySettingsOpen", false);
|
||||
setState(
|
||||
"app",
|
||||
"dialogsOpen",
|
||||
"communitySettingsOpen",
|
||||
action.payload,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ interface IAppState {
|
|||
interface IDialogsOpen {
|
||||
settingsOpen: boolean;
|
||||
addCommunityOpen: boolean;
|
||||
communitySettingsOpen: boolean;
|
||||
}
|
||||
|
||||
export { type IAppState, type IDialogsOpen };
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import {
|
||||
IFetchLoginRequest,
|
||||
IFetchLoginResponse,
|
||||
IFetchRefreshResponse,
|
||||
IFetchRefreshResponseError,
|
||||
IFetchRefreshResponseSuccess,
|
||||
} from "../../api/auth";
|
||||
|
||||
enum AuthActionTypes {
|
||||
SET_LOGGED_IN = "SET_LOGGED_IN",
|
||||
FETCH_LOGIN_START = "FETCH_LOGIN_START",
|
||||
FETCH_LOGIN_FINISH = "FETCH_LOGIN_FINISH",
|
||||
FETCH_REFRESH_START = "FETCH_REFRESH_START",
|
||||
|
|
@ -12,6 +14,7 @@ enum AuthActionTypes {
|
|||
}
|
||||
|
||||
type AuthAction =
|
||||
| { type: AuthActionTypes.SET_LOGGED_IN; payload: boolean }
|
||||
| { type: AuthActionTypes.FETCH_LOGIN_START; payload: IFetchLoginRequest }
|
||||
| {
|
||||
type: AuthActionTypes.FETCH_LOGIN_FINISH;
|
||||
|
|
@ -20,7 +23,7 @@ type AuthAction =
|
|||
| { type: AuthActionTypes.FETCH_REFRESH_START }
|
||||
| {
|
||||
type: AuthActionTypes.FETCH_REFRESH_FINISH;
|
||||
payload: IFetchRefreshResponse;
|
||||
payload: IFetchRefreshResponseError | IFetchRefreshResponseSuccess;
|
||||
};
|
||||
|
||||
export { AuthActionTypes, type AuthAction };
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { IAuthState } from "./types";
|
|||
|
||||
function authReducer(_state: IAuthState, action: AuthAction) {
|
||||
switch (action.type) {
|
||||
case AuthActionTypes.SET_LOGGED_IN:
|
||||
setState("auth", "loggedIn", action.payload);
|
||||
break;
|
||||
case AuthActionTypes.FETCH_LOGIN_START:
|
||||
fetchLogin(action.payload.username, action.payload.password);
|
||||
break;
|
||||
|
|
@ -15,7 +18,9 @@ function authReducer(_state: IAuthState, action: AuthAction) {
|
|||
fetchRefresh();
|
||||
break;
|
||||
case AuthActionTypes.FETCH_REFRESH_FINISH:
|
||||
setState("auth", "session", action.payload);
|
||||
if ("id" in action.payload) {
|
||||
setState("auth", "session", action.payload);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
interface IAuthState {
|
||||
loggedIn?: boolean;
|
||||
session?: ISession;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,16 @@ import {
|
|||
ICreateChannelResponse,
|
||||
IUpdateChannelResponse,
|
||||
IRemoveChannelResponse,
|
||||
IFetchChannelMessagesResponse,
|
||||
IFetchChannelMessage,
|
||||
} from "../../api/channel";
|
||||
import { IFetchCommunityChannel } from "../../api/community";
|
||||
|
||||
enum ChannelActionTypes {
|
||||
SET_CHANNEL = "SET_CHANNEL",
|
||||
SET_ACTIVE_CHANNEL = "SET_ACTIVE_CHANNEL",
|
||||
SET_TEXT = "SET_TEXT",
|
||||
SET_CHANNEL_MESSAGE = "SET_CHANNEL_MESSAGE",
|
||||
FETCH_CHANNEL_START = "FETCH_CHANNEL_START",
|
||||
FETCH_CHANNEL_FINISH = "FETCH_CHANNEL_FINISH",
|
||||
CREATE_CHANNEL_START = "CREATE_CHANNEL_START",
|
||||
|
|
@ -19,6 +23,8 @@ enum ChannelActionTypes {
|
|||
UPDATE_CHANNEL_FINISH = "UPDATE_CHANNEL_FINISH",
|
||||
REMOVE_CHANNEL_START = "REMOVE_CHANNEL_START",
|
||||
REMOVE_CHANNEL_FINISH = "REMOVE_CHANNEL_FINISH",
|
||||
FETCH_CHANNEL_MESSAGES_START = "FETCH_CHANNEL_MESSAGES_START",
|
||||
FETCH_CHANNEL_MESSAGES_FINISH = "FETCH_CHANNEL_MESSAGES_FINISH",
|
||||
}
|
||||
|
||||
type ChannelAction =
|
||||
|
|
@ -30,6 +36,14 @@ type ChannelAction =
|
|||
type: ChannelActionTypes.SET_ACTIVE_CHANNEL;
|
||||
payload: string | undefined;
|
||||
}
|
||||
| {
|
||||
type: ChannelActionTypes.SET_TEXT;
|
||||
payload: { id: string; text: string };
|
||||
}
|
||||
| {
|
||||
type: ChannelActionTypes.SET_CHANNEL_MESSAGE;
|
||||
payload: { id: string; message: IFetchChannelMessage };
|
||||
}
|
||||
| { type: ChannelActionTypes.FETCH_CHANNEL_START; payload: string }
|
||||
| {
|
||||
type: ChannelActionTypes.FETCH_CHANNEL_FINISH;
|
||||
|
|
@ -55,6 +69,11 @@ type ChannelAction =
|
|||
| {
|
||||
type: ChannelActionTypes.REMOVE_CHANNEL_FINISH;
|
||||
payload: IRemoveChannelResponse;
|
||||
}
|
||||
| { type: ChannelActionTypes.FETCH_CHANNEL_MESSAGES_START; payload: string }
|
||||
| {
|
||||
type: ChannelActionTypes.FETCH_CHANNEL_MESSAGES_FINISH;
|
||||
payload: IFetchChannelMessagesResponse;
|
||||
};
|
||||
|
||||
export { ChannelActionTypes, type ChannelAction };
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ import {
|
|||
createChannel,
|
||||
updateChannel,
|
||||
removeChannel,
|
||||
fetchChannelMessages,
|
||||
} from "../../services/channel";
|
||||
import { setState } from "../state";
|
||||
import { ChannelActionTypes, ChannelAction } from "./actions";
|
||||
import { IChannelState } from "./types";
|
||||
|
||||
function channelReducer(state: IChannelState, action: ChannelAction) {
|
||||
function channelReducer(_state: IChannelState, action: ChannelAction) {
|
||||
switch (action.type) {
|
||||
case ChannelActionTypes.SET_CHANNEL:
|
||||
setState("channel", "channels", action.payload.id, action.payload);
|
||||
|
|
@ -16,6 +17,25 @@ function channelReducer(state: IChannelState, action: ChannelAction) {
|
|||
case ChannelActionTypes.SET_ACTIVE_CHANNEL:
|
||||
setState("channel", "active", action.payload);
|
||||
break;
|
||||
case ChannelActionTypes.SET_TEXT:
|
||||
setState(
|
||||
"channel",
|
||||
"channels",
|
||||
action.payload.id,
|
||||
"text",
|
||||
action.payload.text,
|
||||
);
|
||||
break;
|
||||
case ChannelActionTypes.SET_CHANNEL_MESSAGE:
|
||||
setState(
|
||||
"channel",
|
||||
"channels",
|
||||
action.payload.id,
|
||||
"messages",
|
||||
action.payload.message.id,
|
||||
action.payload.message,
|
||||
);
|
||||
break;
|
||||
case ChannelActionTypes.FETCH_CHANNEL_START:
|
||||
fetchChannel(action.payload);
|
||||
break;
|
||||
|
|
@ -48,6 +68,25 @@ function channelReducer(state: IChannelState, action: ChannelAction) {
|
|||
return copy;
|
||||
});
|
||||
break;
|
||||
case ChannelActionTypes.FETCH_CHANNEL_MESSAGES_START:
|
||||
fetchChannelMessages(action.payload);
|
||||
break;
|
||||
case ChannelActionTypes.FETCH_CHANNEL_MESSAGES_FINISH:
|
||||
setState(
|
||||
"channel",
|
||||
"channels",
|
||||
action.payload.id,
|
||||
"messages",
|
||||
(messages) => {
|
||||
const newMessages = Object.fromEntries(
|
||||
action.payload.messages.map((item) => [item.id, item]),
|
||||
);
|
||||
|
||||
const copy = { ...messages, ...newMessages };
|
||||
return copy;
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { IMessage } from "../message";
|
||||
|
||||
interface IChannelState {
|
||||
active?: string;
|
||||
channels: Record<string, IChannel>;
|
||||
|
|
@ -9,6 +11,8 @@ interface IChannel {
|
|||
description?: string;
|
||||
communityId?: string;
|
||||
creationDate?: number;
|
||||
text?: string;
|
||||
messages?: Record<string, IMessage>;
|
||||
}
|
||||
|
||||
export { type IChannelState, type IChannel };
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { setState } from "../state";
|
|||
import { CommunityActionTypes, CommunityAction } from "./actions";
|
||||
import { ICommunityState } from "./types";
|
||||
|
||||
function communityReducer(state: ICommunityState, action: CommunityAction) {
|
||||
function communityReducer(_state: ICommunityState, action: CommunityAction) {
|
||||
switch (action.type) {
|
||||
case CommunityActionTypes.SET_COMMUNITY:
|
||||
setState(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { setState } from "../state";
|
|||
import { InviteActionTypes, InviteAction } from "./actions";
|
||||
import { IInviteState } from "./types";
|
||||
|
||||
function inviteReducer(state: IInviteState, action: InviteAction) {
|
||||
function inviteReducer(_state: IInviteState, action: InviteAction) {
|
||||
switch (action.type) {
|
||||
case InviteActionTypes.SET_INVITE:
|
||||
setState("invite", "invites", action.payload.id, action.payload);
|
||||
|
|
|
|||
49
src/store/message/actions.ts
Normal file
49
src/store/message/actions.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import {
|
||||
ICreateMessageRequest,
|
||||
IUpdateMessageRequest,
|
||||
IFetchMessageResponse,
|
||||
ICreateMessageResponse,
|
||||
IUpdateMessageResponse,
|
||||
IRemoveMessageResponse,
|
||||
} from "../../api/message";
|
||||
|
||||
enum MessageActionTypes {
|
||||
FETCH_MESSAGE_START = "FETCH_MESSAGE_START",
|
||||
FETCH_MESSAGE_FINISH = "FETCH_MESSAGE_FINISH",
|
||||
CREATE_MESSAGE_START = "CREATE_MESSAGE_START",
|
||||
CREATE_MESSAGE_FINISH = "CREATE_MESSAGE_FINISH",
|
||||
UPDATE_MESSAGE_START = "UPDATE_MESSAGE_START",
|
||||
UPDATE_MESSAGE_FINISH = "UPDATE_MESSAGE_FINISH",
|
||||
REMOVE_MESSAGE_START = "REMOVE_MESSAGE_START",
|
||||
REMOVE_MESSAGE_FINISH = "REMOVE_MESSAGE_FINISH",
|
||||
}
|
||||
|
||||
type MessageAction =
|
||||
| { type: MessageActionTypes.FETCH_MESSAGE_START; payload: string }
|
||||
| {
|
||||
type: MessageActionTypes.FETCH_MESSAGE_FINISH;
|
||||
payload: IFetchMessageResponse;
|
||||
}
|
||||
| {
|
||||
type: MessageActionTypes.CREATE_MESSAGE_START;
|
||||
payload: ICreateMessageRequest;
|
||||
}
|
||||
| {
|
||||
type: MessageActionTypes.CREATE_MESSAGE_FINISH;
|
||||
payload: ICreateMessageResponse;
|
||||
}
|
||||
| {
|
||||
type: MessageActionTypes.UPDATE_MESSAGE_START;
|
||||
payload: IUpdateMessageRequest;
|
||||
}
|
||||
| {
|
||||
type: MessageActionTypes.UPDATE_MESSAGE_FINISH;
|
||||
payload: IUpdateMessageResponse;
|
||||
}
|
||||
| { type: MessageActionTypes.REMOVE_MESSAGE_START; payload: string }
|
||||
| {
|
||||
type: MessageActionTypes.REMOVE_MESSAGE_FINISH;
|
||||
payload: IRemoveMessageResponse;
|
||||
};
|
||||
|
||||
export { MessageActionTypes, type MessageAction };
|
||||
3
src/store/message/index.ts
Normal file
3
src/store/message/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./message";
|
||||
export * from "./actions";
|
||||
export * from "./types";
|
||||
59
src/store/message/message.ts
Normal file
59
src/store/message/message.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import {
|
||||
fetchMessage,
|
||||
createMessage,
|
||||
updateMessage,
|
||||
removeMessage,
|
||||
} from "../../services/message";
|
||||
import { setState } from "../state";
|
||||
import { MessageActionTypes, MessageAction } from "./actions";
|
||||
import { IMessageState } from "./types";
|
||||
|
||||
function messageReducer(_state: IMessageState, action: MessageAction) {
|
||||
switch (action.type) {
|
||||
case MessageActionTypes.FETCH_MESSAGE_START:
|
||||
fetchMessage(action.payload);
|
||||
break;
|
||||
case MessageActionTypes.FETCH_MESSAGE_FINISH:
|
||||
setState("message", "message", action.payload);
|
||||
setState(
|
||||
"channel",
|
||||
"channels",
|
||||
action.payload.channelId,
|
||||
action.payload,
|
||||
);
|
||||
break;
|
||||
case MessageActionTypes.CREATE_MESSAGE_START:
|
||||
createMessage(action.payload.text, action.payload.channelId);
|
||||
break;
|
||||
case MessageActionTypes.CREATE_MESSAGE_FINISH:
|
||||
setState("message", "message", action.payload);
|
||||
setState(
|
||||
"channel",
|
||||
"channels",
|
||||
action.payload.channelId,
|
||||
"messages",
|
||||
action.payload.id,
|
||||
action.payload,
|
||||
);
|
||||
break;
|
||||
case MessageActionTypes.UPDATE_MESSAGE_START:
|
||||
updateMessage(action.payload.id, action.payload.text);
|
||||
break;
|
||||
case MessageActionTypes.UPDATE_MESSAGE_FINISH:
|
||||
setState("message", "message", action.payload);
|
||||
setState(
|
||||
"channel",
|
||||
"channels",
|
||||
action.payload.channelId,
|
||||
action.payload,
|
||||
);
|
||||
break;
|
||||
case MessageActionTypes.REMOVE_MESSAGE_START:
|
||||
removeMessage(action.payload);
|
||||
break;
|
||||
case MessageActionTypes.REMOVE_MESSAGE_FINISH:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export { messageReducer };
|
||||
15
src/store/message/types.ts
Normal file
15
src/store/message/types.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
interface IMessageState {
|
||||
message?: IMessage;
|
||||
}
|
||||
|
||||
interface IMessage {
|
||||
id: string;
|
||||
text: string;
|
||||
editHistory?: string[];
|
||||
edited: boolean;
|
||||
ownerId: string;
|
||||
channelId?: string;
|
||||
creationDate: number;
|
||||
}
|
||||
|
||||
export { type IMessageState, type IMessage };
|
||||
|
|
@ -8,6 +8,7 @@ import { ChannelAction, channelReducer } from "./channel";
|
|||
import { RoleAction, roleReducer } from "./role";
|
||||
import { SessionAction, sessionReducer } from "./session";
|
||||
import { InviteAction, inviteReducer } from "./invite";
|
||||
import { MessageAction, messageReducer } from "./message";
|
||||
|
||||
function reducer(state: IState, action: Action) {
|
||||
appReducer(state.app, action as AppAction);
|
||||
|
|
@ -18,6 +19,7 @@ function reducer(state: IState, action: Action) {
|
|||
roleReducer(state.role, action as RoleAction);
|
||||
sessionReducer(state.session, action as SessionAction);
|
||||
inviteReducer(state.invite, action as InviteAction);
|
||||
messageReducer(state.message, action as MessageAction);
|
||||
}
|
||||
|
||||
export { reducer };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { IFetchCommunityRole } from "../../api/community";
|
||||
import {
|
||||
ICreateRoleRequest,
|
||||
IUpdateRoleRequest,
|
||||
IFetchRoleResponse,
|
||||
ICreateRoleResponse,
|
||||
IUpdateRoleResponse,
|
||||
|
|
@ -34,7 +35,7 @@ type RoleAction =
|
|||
type: RoleActionTypes.CREATE_ROLE_FINISH;
|
||||
payload: ICreateRoleResponse;
|
||||
}
|
||||
| { type: RoleActionTypes.UPDATE_ROLE_START; payload: string }
|
||||
| { type: RoleActionTypes.UPDATE_ROLE_START; payload: IUpdateRoleRequest }
|
||||
| {
|
||||
type: RoleActionTypes.UPDATE_ROLE_FINISH;
|
||||
payload: IUpdateRoleResponse;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { setState } from "../state";
|
|||
import { RoleActionTypes, RoleAction } from "./actions";
|
||||
import { IRoleState } from "./types";
|
||||
|
||||
function roleReducer(state: IRoleState, action: RoleAction) {
|
||||
function roleReducer(_state: IRoleState, action: RoleAction) {
|
||||
switch (action.type) {
|
||||
case RoleActionTypes.SET_ROLE:
|
||||
setState("role", "roles", action.payload.id, action.payload);
|
||||
|
|
@ -26,7 +26,7 @@ function roleReducer(state: IRoleState, action: RoleAction) {
|
|||
setState("role", "roles", action.payload.id, action.payload);
|
||||
break;
|
||||
case RoleActionTypes.UPDATE_ROLE_START:
|
||||
updateRole(action.payload);
|
||||
updateRole(action.payload.id, action.payload.name);
|
||||
break;
|
||||
case RoleActionTypes.UPDATE_ROLE_FINISH:
|
||||
setState("role", "roles", action.payload.id, action.payload);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { setState } from "../state";
|
|||
import { SessionActionTypes, SessionAction } from "./actions";
|
||||
import { ISessionState } from "./types";
|
||||
|
||||
function sessionReducer(state: ISessionState, action: SessionAction) {
|
||||
function sessionReducer(_state: ISessionState, action: SessionAction) {
|
||||
switch (action.type) {
|
||||
case SessionActionTypes.SET_SESSION:
|
||||
setState("session", "sessions", action.payload.id, action.payload);
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ const [state, setState] = createStore<IState>({
|
|||
dialogsOpen: {
|
||||
settingsOpen: false,
|
||||
addCommunityOpen: false,
|
||||
communitySettingsOpen: false,
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
loggedIn: undefined,
|
||||
session: undefined,
|
||||
},
|
||||
user: {
|
||||
|
|
@ -33,6 +35,9 @@ const [state, setState] = createStore<IState>({
|
|||
invite: {
|
||||
invites: {},
|
||||
},
|
||||
message: {
|
||||
message: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
function dispatch(action: Action) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { IChannelState } from "./channel";
|
|||
import { IRoleState } from "./role";
|
||||
import { ISessionState } from "./session";
|
||||
import { IInviteState } from "./invite";
|
||||
import { IMessageState } from "./message";
|
||||
|
||||
interface IState {
|
||||
app: IAppState;
|
||||
|
|
@ -16,6 +17,7 @@ interface IState {
|
|||
role: IRoleState;
|
||||
session: ISessionState;
|
||||
invite: IInviteState;
|
||||
message: IMessageState;
|
||||
}
|
||||
|
||||
export { type IState };
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { setState } from "../state";
|
|||
import { UserActionTypes, UserAction } from "./actions";
|
||||
import { IUserState } from "./types";
|
||||
|
||||
function userReducer(state: IUserState, action: UserAction) {
|
||||
function userReducer(_state: IUserState, action: UserAction) {
|
||||
switch (action.type) {
|
||||
case UserActionTypes.SET_USER:
|
||||
setState("user", "users", action.payload.id, action.payload);
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ import { AuthActionTypes } from "../../store/auth";
|
|||
import { HomeView } from "../HomeView";
|
||||
import { CommunityView } from "../CommunityView";
|
||||
import { ModalView } from "../ModalView";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { connectWs } from "../../services/websocket";
|
||||
|
||||
const AppView: Component = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const MainView: Component = () => {
|
||||
onMount(() => {
|
||||
dispatch({
|
||||
type: AuthActionTypes.FETCH_REFRESH_START,
|
||||
|
|
@ -31,6 +35,14 @@ const MainView: Component = () => {
|
|||
}
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (state.auth.loggedIn === false) {
|
||||
navigate("/login");
|
||||
} else {
|
||||
connectWs();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="flex flex-row h-screen">
|
||||
<ModalView />
|
||||
|
|
@ -50,4 +62,4 @@ const MainView: Component = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export { MainView };
|
||||
export { AppView };
|
||||
1
src/views/AppView/index.ts
Normal file
1
src/views/AppView/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./AppView";
|
||||
|
|
@ -4,6 +4,7 @@ import { CommunityActionTypes, ICommunity } from "../../store/community";
|
|||
import { ChannelActionTypes } from "../../store/channel";
|
||||
import { Channel } from "../../components/Channel";
|
||||
import { CommunityBar } from "../../components/CommunityBar";
|
||||
import { AppActionTypes } from "../../store/app";
|
||||
|
||||
const ChannelView: Component = () => {
|
||||
const channelIds = createMemo(() => {
|
||||
|
|
@ -34,6 +35,13 @@ const ChannelView: Component = () => {
|
|||
}
|
||||
});
|
||||
|
||||
const onCommunitySettingsClick = () => {
|
||||
dispatch({
|
||||
type: AppActionTypes.SET_ADD_COMMUNITY_SETTINGS_OPEN,
|
||||
payload: true,
|
||||
});
|
||||
};
|
||||
|
||||
const onChannelClick = (id: string) => {
|
||||
dispatch({
|
||||
type: ChannelActionTypes.FETCH_CHANNEL_START,
|
||||
|
|
@ -70,6 +78,7 @@ const ChannelView: Component = () => {
|
|||
avatar={
|
||||
"https://img.daisyui.com/images/profile/demo/yellingcat@192.webp"
|
||||
}
|
||||
onSettingsClick={onCommunitySettingsClick}
|
||||
/>
|
||||
<ul class="h-full list flex flex-col p-2 gap-1 pt-18 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-500 scrollbar-track-gray-800">
|
||||
{channelIds().map(mapChannel)}
|
||||
|
|
|
|||
|
|
@ -1,189 +1,77 @@
|
|||
import { createMemo, onMount, type Component } from "solid-js";
|
||||
import { createEffect, createMemo, onMount, type Component } from "solid-js";
|
||||
import { ChannelBar } from "../../components/ChannelBar";
|
||||
import { MessageBar } from "../../components/MessageBar";
|
||||
import { Message } from "../../components/Message";
|
||||
import { dispatch, state } from "../../store/state";
|
||||
import { UserActionTypes } from "../../store/user";
|
||||
import { IChannel } from "../../store/channel";
|
||||
import { ChannelActionTypes, IChannel } from "../../store/channel";
|
||||
import { MessageActionTypes } from "../../store/message";
|
||||
|
||||
const ChatView: Component = () => {
|
||||
const testMessages = [
|
||||
{
|
||||
messageId: "432432",
|
||||
message: "Hello this is a test message",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432433",
|
||||
message: "Hi hi",
|
||||
userId: "12122",
|
||||
username: "TestUser",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
{
|
||||
messageId: "432434",
|
||||
message: "Nooooo",
|
||||
userId: "12121",
|
||||
username: "Aslan",
|
||||
avatar: "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp",
|
||||
},
|
||||
];
|
||||
let scrollRef: HTMLUListElement | undefined;
|
||||
let lastScrollTop = 0;
|
||||
let autoScroll = true;
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (!scrollRef || !autoScroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollRef.scrollTop = scrollRef.scrollHeight;
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!scrollRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scrollRef.scrollTop < lastScrollTop) {
|
||||
autoScroll = false;
|
||||
}
|
||||
if (
|
||||
scrollRef.scrollTop + scrollRef.clientHeight >=
|
||||
scrollRef.scrollHeight
|
||||
) {
|
||||
autoScroll = true;
|
||||
}
|
||||
|
||||
lastScrollTop = scrollRef.scrollTop;
|
||||
};
|
||||
|
||||
const channelInfo = createMemo<IChannel | undefined>(() => {
|
||||
const activeChannelId = state.channel.active;
|
||||
return state.channel.channels[activeChannelId ?? 0];
|
||||
if (!activeChannelId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
autoScroll = true;
|
||||
|
||||
dispatch({
|
||||
type: ChannelActionTypes.FETCH_CHANNEL_MESSAGES_START,
|
||||
payload: activeChannelId,
|
||||
});
|
||||
return state.channel.channels[activeChannelId];
|
||||
});
|
||||
|
||||
const messages = createMemo(() => {
|
||||
const channelId = channelInfo()?.id;
|
||||
if (!channelId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const channel = state.channel.channels[channelId];
|
||||
if (!channel) {
|
||||
return [];
|
||||
}
|
||||
|
||||
Object.keys(channel.messages ?? []).length;
|
||||
|
||||
setTimeout(scrollToBottom, 20);
|
||||
setTimeout(scrollToBottom, 100);
|
||||
|
||||
return channel.messages ?? [];
|
||||
});
|
||||
|
||||
let scrollRef: HTMLUListElement | undefined;
|
||||
onMount(() => {
|
||||
if (scrollRef) {
|
||||
scrollRef.scrollTop = scrollRef.scrollHeight;
|
||||
|
|
@ -197,6 +85,50 @@ const ChatView: Component = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const onChangeMessageText = (text: string) => {
|
||||
const channel = channelInfo();
|
||||
if (!channel?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ChannelActionTypes.SET_TEXT,
|
||||
payload: {
|
||||
id: channel.id,
|
||||
text: text,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onMessageSend = () => {
|
||||
autoScroll = true;
|
||||
|
||||
const channel = channelInfo();
|
||||
if (!channel?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = channel.text;
|
||||
if (!text || text.trim().length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ChannelActionTypes.SET_TEXT,
|
||||
payload: {
|
||||
id: channel.id,
|
||||
text: "",
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: MessageActionTypes.CREATE_MESSAGE_START,
|
||||
payload: {
|
||||
channelId: channel.id,
|
||||
text: text,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="bg-stone-800 flex-1 z-0 relative">
|
||||
<div class="h-full">
|
||||
|
|
@ -207,20 +139,29 @@ const ChatView: Component = () => {
|
|||
/>
|
||||
<ul
|
||||
ref={scrollRef}
|
||||
onScrollEnd={handleScroll}
|
||||
class="h-full list flex flex-col p-2 pt-18 pb-24 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-500 scrollbar-track-gray-800"
|
||||
>
|
||||
{testMessages.map((msg) => (
|
||||
{Object.entries(messages()).map(([_key, message]) => (
|
||||
<Message
|
||||
messageId={msg.messageId}
|
||||
message={msg.message}
|
||||
userId={msg.userId}
|
||||
username={msg.username}
|
||||
avatar={msg.avatar}
|
||||
messageId={message.id}
|
||||
message={message.text}
|
||||
userId={message.ownerId}
|
||||
username={
|
||||
state.user.users[message.ownerId].username ?? ""
|
||||
}
|
||||
avatar={
|
||||
"https://img.daisyui.com/images/profile/demo/yellingcat@192.webp"
|
||||
}
|
||||
onProfileClick={onProfileClick}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<MessageBar />
|
||||
<MessageBar
|
||||
text={channelInfo()?.text ?? ""}
|
||||
onChangeText={onChangeMessageText}
|
||||
onSend={onMessageSend}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createEffect, createSignal, type Component } from "solid-js";
|
||||
import { createEffect, createSignal, onMount, type Component } from "solid-js";
|
||||
import { dispatch, state } from "../../store/state";
|
||||
import { AuthActionTypes } from "../../store/auth";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
|
|
@ -9,6 +9,12 @@ const LoginView: Component = () => {
|
|||
|
||||
const navigate = useNavigate();
|
||||
|
||||
onMount(() => {
|
||||
dispatch({
|
||||
type: AuthActionTypes.FETCH_REFRESH_START,
|
||||
});
|
||||
});
|
||||
|
||||
const onLogin = () => {
|
||||
dispatch({
|
||||
type: AuthActionTypes.FETCH_LOGIN_START,
|
||||
|
|
@ -20,8 +26,8 @@ const LoginView: Component = () => {
|
|||
};
|
||||
|
||||
createEffect(() => {
|
||||
if (state.auth.session?.id) {
|
||||
navigate("/");
|
||||
if (state.auth.loggedIn) {
|
||||
navigate("/app");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export * from "./MainView";
|
||||
|
|
@ -2,11 +2,13 @@ import { createEffect, type Component } from "solid-js";
|
|||
import { dispatch, state } from "../../store/state";
|
||||
import SettingsModal from "../../components/SettingsModal/SettingsModal";
|
||||
import CommunityModal from "../../components/CommunityModal/CommunityModal";
|
||||
import CommunitySettingsModal from "../../components/CommunitySettingsModal/CommunitySettingsModal";
|
||||
import { AppActionTypes } from "../../store/app";
|
||||
|
||||
const ModalView: Component = () => {
|
||||
let settingsModal: HTMLDialogElement;
|
||||
let communityModal: HTMLDialogElement;
|
||||
let communitySettingsModal: HTMLDialogElement;
|
||||
|
||||
createEffect(() => {
|
||||
if (state.app.dialogsOpen.settingsOpen) {
|
||||
|
|
@ -24,6 +26,14 @@ const ModalView: Component = () => {
|
|||
}
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (state.app.dialogsOpen.communitySettingsOpen) {
|
||||
communitySettingsModal.showModal();
|
||||
} else {
|
||||
communitySettingsModal.close();
|
||||
}
|
||||
});
|
||||
|
||||
const onCloseSettings = () => {
|
||||
dispatch({
|
||||
type: AppActionTypes.SET_SETTINGS_OPEN,
|
||||
|
|
@ -38,6 +48,13 @@ const ModalView: Component = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const onCloseCommunitySettings = () => {
|
||||
dispatch({
|
||||
type: AppActionTypes.SET_ADD_COMMUNITY_SETTINGS_OPEN,
|
||||
payload: false,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsModal
|
||||
|
|
@ -48,6 +65,10 @@ const ModalView: Component = () => {
|
|||
dialogRef={(element) => (communityModal = element)}
|
||||
onClose={onCloseCommunity}
|
||||
/>
|
||||
<CommunitySettingsModal
|
||||
dialogRef={(element) => (communitySettingsModal = element)}
|
||||
onClose={onCloseCommunitySettings}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,27 @@
|
|||
import type { Component } from "solid-js";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { createSignal, createEffect, type Component, onMount } from "solid-js";
|
||||
import { dispatch, state } from "../../store/state";
|
||||
import { AuthActionTypes } from "../../store/auth";
|
||||
|
||||
const RegisterView: Component = () => {
|
||||
const [getUsername, setUsername] = createSignal("");
|
||||
const [getEmail, setEmail] = createSignal("");
|
||||
const [getPassword, setPassword] = createSignal("");
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
onMount(() => {
|
||||
dispatch({
|
||||
type: AuthActionTypes.FETCH_REFRESH_START,
|
||||
});
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (state.auth.loggedIn) {
|
||||
navigate("/app");
|
||||
}
|
||||
});
|
||||
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import type { Component } from "solid-js";
|
||||
|
||||
const SettingsView: Component = () => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export { SettingsView };
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from "./SettingsView";
|
||||
7
src/views/StartView/StartView.tsx
Normal file
7
src/views/StartView/StartView.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { type Component } from "solid-js";
|
||||
|
||||
const StartView: Component = () => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export { StartView };
|
||||
1
src/views/StartView/index.ts
Normal file
1
src/views/StartView/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./StartView";
|
||||
Loading…
Add table
Add a link
Reference in a new issue