From 3b625c7694aee05282621cc65b53a57f81c739d0 Mon Sep 17 00:00:00 2001 From: Abdalla Abdelhadi <97321669+AbdallaAbdelhadi@users.noreply.github.com> Date: Thu, 22 Feb 2024 20:54:46 -0500 Subject: [PATCH 1/3] Resolve DOO-78 (#70) * add ability to add, delete and like comments * Fix collab colors * Fix linting * fix comments --- client/src/Bootstrap.tsx | 3 + client/src/WebsocketClient.ts | 17 +- client/src/components/lib/BoardHeader.tsx | 10 +- client/src/components/lib/BoardScroll.tsx | 13 +- client/src/components/lib/Canvas.tsx | 12 +- .../components/lib/CommentsSheetContent.tsx | 299 ++++++++++++++---- .../src/components/lib/DeleteCanvasDialog.tsx | 1 + client/src/components/lib/TopBar.tsx | 6 + client/src/constants.ts | 6 + client/src/hooks/useSocket.tsx | 7 +- client/src/lib/misc.ts | 11 + client/src/stores/AuthStore.ts | 3 +- client/src/stores/CanavasBoardStore.ts | 5 +- client/src/stores/CommentsStore.ts | 117 +++++++ client/src/stores/WebSocketStore.ts | 25 +- client/src/views/SignInPage.tsx | 43 ++- client/src/views/SignUpPage.tsx | 4 + node/src/api/board/board.controller.ts | 50 +-- .../collaborator/collaborator.controller.ts | 4 +- node/src/api/comment/comment.controller.ts | 91 +++++- node/src/api/comment/comment.route.ts | 8 + node/src/models/board.ts | 2 +- node/src/models/collaborator.ts | 13 + node/src/models/comment.ts | 22 +- node/src/utils/misc.ts | 14 + 25 files changed, 656 insertions(+), 130 deletions(-) create mode 100644 client/src/stores/CommentsStore.ts diff --git a/client/src/Bootstrap.tsx b/client/src/Bootstrap.tsx index 0e81bad..c514ebe 100644 --- a/client/src/Bootstrap.tsx +++ b/client/src/Bootstrap.tsx @@ -6,6 +6,7 @@ import { ACCESS_TOKEN_TAG, HTTP_STATUS } from './constants'; import { useAuthStore } from './stores/AuthStore'; import { useCanvasBoardStore } from './stores/CanavasBoardStore'; import { useCanvasElementStore } from './stores/CanvasElementsStore'; +import { useCommentsStore } from './stores/CommentsStore'; /** * @author Zakariyya Almalki @@ -22,6 +23,7 @@ const Bootstrap = () => { const { setCanvasElementState } = useCanvasElementStore([ 'setCanvasElementState', ]); + const { setColorMaping } = useCommentsStore(['setColorMaping']); const [isLoaded, setIsLoaded] = useState(false); const auth = async () => { const token = localStorage.getItem(ACCESS_TOKEN_TAG); @@ -36,6 +38,7 @@ const Bootstrap = () => { setCanvases, setBoardMeta, setCanvasElementState, + setColorMaping, ) )?.valueOf(); if (response.status === HTTP_STATUS.SUCCESS) { diff --git a/client/src/WebsocketClient.ts b/client/src/WebsocketClient.ts index b65fd12..f70b6aa 100644 --- a/client/src/WebsocketClient.ts +++ b/client/src/WebsocketClient.ts @@ -14,6 +14,7 @@ import { CanvasElement } from '@/stores/CanvasElementsStore'; import { EVENT } from './types'; import { ValueOf } from './lib/misc'; import { UpdatedTimeMessage } from './stores/WebSocketStore'; +import { Comment } from './stores/CommentsStore'; interface CallBacksType { addCanvasShape: (element: CanvasElement) => void; @@ -33,6 +34,7 @@ interface WSMessageType { // Encapsualtes functionality to interact with websockets export default class WebsocketClient { userId: string; + _userId: string; reconnectInterval: number; socket: WebSocket | null; room: string | null; //the current room the socket is in @@ -57,6 +59,7 @@ export default class WebsocketClient { this.callBacks = callBacks; this.injectableCallbacks = {}; this.userId = userId; + this._userId = userId; this.reconnectInterval = reconnectInterval; this.connect(); // Create a socket } @@ -162,7 +165,8 @@ export default class WebsocketClient { if (injectableCallbacks !== undefined) { injectableCallbacks(jsonMsg); } else { - this.callBacks[jsonMsg.topic](jsonMsg.payload); + const callback = this.callBacks[jsonMsg.topic]; + callback && callback(jsonMsg.payload); } }); } @@ -206,7 +210,13 @@ export default class WebsocketClient { */ async sendMsgRoom( topic: string, - msg: CanvasElement | string | string[] | UpdatedTimeMessage | null, + msg: + | CanvasElement + | string + | string[] + | UpdatedTimeMessage + | null + | { elemID: string; comment: Partial }, ) { // Msg to be changed to proper type once everything finalized if (this.room === null) throw 'No room assigned!'; @@ -224,8 +234,9 @@ export default class WebsocketClient { * * @param room String, the room id */ - async joinRoom(room: string) { + async joinRoom(room: string, collabID: string) { this.room = room; + this.userId = `${this._userId}-${collabID}`; return this.send({ topic: WS_TOPICS.JOIN_ROOM, room: this.room, diff --git a/client/src/components/lib/BoardHeader.tsx b/client/src/components/lib/BoardHeader.tsx index 2654c84..1e3167d 100644 --- a/client/src/components/lib/BoardHeader.tsx +++ b/client/src/components/lib/BoardHeader.tsx @@ -50,6 +50,7 @@ const BoardHeader = ({ 'setIsUsingStableDiffusion', ]); const { + selectedElementIds, allIds, types, strokeColors, @@ -72,6 +73,7 @@ const BoardHeader = ({ fileIds, resetCanvas, } = useCanvasElementStore([ + 'selectedElementIds', 'allIds', 'types', 'strokeColors', @@ -127,6 +129,7 @@ const BoardHeader = ({ lastModified: '', roomID: '', shareUrl: '', + collabID: '', }); }} > @@ -161,14 +164,15 @@ const BoardHeader = ({ setIsViewingComments(!isViewingComments); !isViewingComments && setIsUsingStableDiffusion(false); }} + disabled={selectedElementIds.length !== 1} > {/* Share Dialog */}
))} @@ -180,21 +303,69 @@ const CommentsSheetContent = () => { + + )} +
+ {boardMeta.tags.map((tag, index) => ( +
+ setTag(boardMeta.tags.filter((item) => item !== tag)) + } + key={index} + className="bg-gray-200 rounded-md p-2 text-sm" + > + {tag} +
+ ))} +
+ + + + Cancel + + { + const newTitle = titleRef.current?.value; + const newFolder = folderRef.current?.value; + const updatedAt = await axios.put(REST.board.updateBoard, { + id: boardMeta.id, + fields: { + title: newTitle, + tags: boardMeta.tags, + folder: newFolder, + }, + }); + setBoardMeta({ + title: newTitle, + tags: boardMeta.tags, + folder: newFolder, + lastModified: updatedAt.data.updatedAt, + }); + updateCanvas(boardMeta.id, updatedAt.data.updatedAt); + updateCanvasInfo( + boardMeta.id, + newTitle ?? '', + newFolder ?? '', + boardMeta.tags, + ); + setWebsocketAction( + { + boardID: boardMeta.id, + lastModified: updatedAt.data.updatedAt, + }, + 'updateUpdatedTime', + ); + }} + > + Continue + + + + + ); +} diff --git a/client/src/components/lib/TopBar.tsx b/client/src/components/lib/TopBar.tsx index c5eb446..8c8d70d 100644 --- a/client/src/components/lib/TopBar.tsx +++ b/client/src/components/lib/TopBar.tsx @@ -98,6 +98,8 @@ export const TopBar = () => { id: boardData.id, lastModified: boardData.updatedAt, shareUrl: boardData.shareUrl, + folder: boardData.folder, + tags: boardData.tags, collabID: data.data.collabID, }); diff --git a/client/src/stores/CanavasBoardStore.ts b/client/src/stores/CanavasBoardStore.ts index a153783..a63f8f8 100644 --- a/client/src/stores/CanavasBoardStore.ts +++ b/client/src/stores/CanavasBoardStore.ts @@ -4,7 +4,7 @@ import { createStoreWithSelectors } from './utils'; /** * Define Global CanvasBoard states and reducers - * @author Abdalla Abdelhadi + * @author Abdalla Abdelhadi, Zakariyya Almalki */ export const boards = ['Folder', 'Templates', 'Settings'] as const; @@ -37,6 +37,8 @@ interface CanvasBoardState { lastModified: string; roomID: string; shareUrl: string; + folder: string; + tags: string[]; collabID: string; }; } @@ -48,7 +50,14 @@ interface CanvasBoardActions { addCanvas: (canvas: Canvas) => void; removeCanvas: (id: string) => void; updateCanvas: (id: string, meta: string) => void; + updateCanvasInfo: ( + id: string, + title: string, + folder: string, + tags: string[], + ) => void; setBoardMeta: (meta: Partial) => void; + setTag: (tags: Array) => void; } type CanvasBoardStore = CanvasBoardActions & CanvasBoardState; @@ -64,6 +73,8 @@ export const initialCanvasState: CanvasBoardState = { lastModified: '', roomID: '', shareUrl: '', + folder: '', + tags: [], collabID: '', }, }; @@ -101,12 +112,26 @@ const updateCanvas = return { ...state, canvases }; }); +const updateCanvasInfo = + (set: SetState) => + (id: string, title: string, folder: string, tags: string[]) => + set((state) => { + const canvases = state.canvases.map((canvas) => + canvas.id === id ? { ...canvas, title, folder, tags } : canvas, + ); + + return { ...state, canvases }; + }); + const setBoardMeta = (set: SetState) => (meta: Partial) => { set((state) => ({ boardMeta: { ...state.boardMeta, ...meta } })); }; +const setTag = (set: SetState) => (tags: Array) => + set((state) => ({ boardMeta: { ...state.boardMeta, tags } })); + /** Store Hook */ const CanvasBoardStore = create()((set) => ({ ...initialCanvasState, @@ -115,6 +140,8 @@ const CanvasBoardStore = create()((set) => ({ addCanvas: addCanvas(set), removeCanvas: removeCanvas(set), updateCanvas: updateCanvas(set), + updateCanvasInfo: updateCanvasInfo(set), setBoardMeta: setBoardMeta(set), + setTag: setTag(set), })); export const useCanvasBoardStore = createStoreWithSelectors(CanvasBoardStore); diff --git a/client/src/views/SignInPage.tsx b/client/src/views/SignInPage.tsx index 74510f8..0c6a7c8 100644 --- a/client/src/views/SignInPage.tsx +++ b/client/src/views/SignInPage.tsx @@ -83,6 +83,8 @@ export async function getUserDetails( lastModified: string; roomID: string; shareUrl: string; + folder: string; + tags: string[]; collabID: string; }>, ) => void, @@ -128,6 +130,8 @@ export const checkURL = async ( lastModified: string; roomID: string; shareUrl: string; + folder: string; + tags: string[]; collabID: string; }>, ) => void, @@ -145,7 +149,6 @@ export const checkURL = async ( id: queryParams.get('boardID'), fields: { collaborators: userID }, }); - setColorMaping(board.data.collaborators); setBoardMeta({ @@ -154,6 +157,8 @@ export const checkURL = async ( id: board.data.uid, lastModified: board.data.updatedAt, shareUrl: board.data.shareUrl, + folder: board.data.folder, + tags: board.data.tags, collabID: board.data.collabID, }); diff --git a/client/src/views/Viewport.tsx b/client/src/views/Viewport.tsx index c0b3a91..9413186 100644 --- a/client/src/views/Viewport.tsx +++ b/client/src/views/Viewport.tsx @@ -21,12 +21,13 @@ import BoardHeader from '@/components/lib/BoardHeader'; import ShareBoardDialog from '@/components/lib/ShareBoardDialog'; import { users } from '@/stores/WebSocketStore'; import { useCanvasBoardStore } from '@/stores/CanavasBoardStore'; +import EditBoardDataDialog from '@/components/lib/EditBoardDataDialog'; /** * Primary viewport that houses the canvas * and accompanying widgets/buttons that lie * on top of it (absolutely positioned). - * @authors Yousef Yassin + * @authors Yousef Yassin, Abdalla Abdelhadi, Zakariyya Almalki */ const Viewport = () => { const { tool } = useAppStore(['tool']); @@ -35,6 +36,7 @@ const Viewport = () => { const isDrawingSelected = isDrawingTool(tool); const [isShareDialogOpen, setIsShareDialogOpen] = useState(false); const { boardMeta } = useCanvasBoardStore(['boardMeta']); + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); return ( @@ -61,6 +63,10 @@ const Viewport = () => { boardLink={boardMeta.shareUrl} users={users} /> +
{ {(selectedElementIds.length === 1 || isDrawingSelected) && ( )} - +
{ @FastFireField({ required: true }) tags!: string[]; @FastFireField({ required: true }) - folder!: string[]; + folder!: string; @FastFireField({ required: true }) shareUrl!: string; @FastFireField({ required: true })