diff --git a/front/src/components/whiteboard/WhiteboardCanvas.vue b/front/src/components/whiteboard/WhiteboardCanvas.vue new file mode 100644 index 0000000..cc5aafd --- /dev/null +++ b/front/src/components/whiteboard/WhiteboardCanvas.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/front/src/components/whiteboard/WhiteboardToolbar.vue b/front/src/components/whiteboard/WhiteboardToolbar.vue new file mode 100644 index 0000000..904622c --- /dev/null +++ b/front/src/components/whiteboard/WhiteboardToolbar.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/front/src/components/whiteboard/shapes/SvgDraftRect.vue b/front/src/components/whiteboard/shapes/SvgDraftRect.vue new file mode 100644 index 0000000..17632e1 --- /dev/null +++ b/front/src/components/whiteboard/shapes/SvgDraftRect.vue @@ -0,0 +1,22 @@ + + + diff --git a/front/src/components/whiteboard/shapes/SvgRectangle.vue b/front/src/components/whiteboard/shapes/SvgRectangle.vue new file mode 100644 index 0000000..95aedb3 --- /dev/null +++ b/front/src/components/whiteboard/shapes/SvgRectangle.vue @@ -0,0 +1,17 @@ + + + diff --git a/front/src/services/whiteboardHubService.ts b/front/src/services/whiteboardHubService.ts new file mode 100644 index 0000000..9945d81 --- /dev/null +++ b/front/src/services/whiteboardHubService.ts @@ -0,0 +1,49 @@ +import { SignalRService } from '@/services/signalr.ts' +import type { Rectangle, Whiteboard } from '@/types/whiteboard.ts' + +const client = new SignalRService(`http://localhost:5039/whiteboardhub`) + +export const whiteboardHubService = { + async connect() { + await client.start() + }, + + async disconnect() { + await client.stop() + }, + + async joinWhiteboard(id: string) { + await client.invoke('JoinWhiteboard', id) + }, + + async leaveWhiteboard(id: string) { + await client.invoke('LeaveWhiteboard', id) + }, + + async addRectangle(rectangle: Rectangle) { + await client.invoke('AddRectangle', rectangle) + }, + + onInitWhiteboard(callback: (whiteboard: Whiteboard) => void) { + client.on('InitWhiteboard', callback) + }, + + onAddedRectangle(callback: (rectangle: Rectangle) => void) { + client.on('AddedRectangle', callback) + }, + + onJoined(callback: (userId: string) => void) { + client.on('Joined', callback) + }, + + onLeaved(callback: (userId: string) => void) { + client.on('Leaved', callback) + }, + + offAll() { + client.off('InitWhiteboard') + client.off('AddedRectangle') + client.off('Joined') + client.off('Leaved') + }, +} diff --git a/front/src/stores/whiteboard.ts b/front/src/stores/whiteboard.ts new file mode 100644 index 0000000..1a3881a --- /dev/null +++ b/front/src/stores/whiteboard.ts @@ -0,0 +1,88 @@ +import { ref } from 'vue' +import { defineStore } from 'pinia' +import type { Rectangle, ShapeTool, Whiteboard } from '@/types/whiteboard.ts' +import { whiteboardHubService } from '@/services/whiteboardHubService.ts' + +export const useWhiteboardStore = defineStore('whiteboard', () => { + const whiteboard = ref(null) + const selectedTool = ref('rectangle') + const isConnected = ref(false) + const isLoading = ref(false) + const error = ref(null) + + async function joinWhiteboard(id: string) { + isLoading.value = true + error.value = null + + try { + await whiteboardHubService.connect() + isConnected.value = true + + whiteboardHubService.onInitWhiteboard((wb) => { + whiteboard.value = wb + isLoading.value = false + }) + + whiteboardHubService.onAddedRectangle((rectangle) => { + whiteboard.value?.rectangles.push(rectangle) + }) + + whiteboardHubService.onJoined((userId) => { + console.log('User joined:', userId) + }) + + whiteboardHubService.onLeaved((userId) => { + console.log('User left:', userId) + }) + + await whiteboardHubService.joinWhiteboard(id) + } catch (e: any) { + error.value = e?.message ?? 'Failed to join whiteboard' + isLoading.value = false + } + } + + async function leaveWhiteboard() { + if (!whiteboard.value) return + + try { + await whiteboardHubService.leaveWhiteboard(whiteboard.value.whiteboardId) + } catch (e) { + console.warn('Leave request failed', e) + } + + whiteboardHubService.offAll() + await whiteboardHubService.disconnect() + + whiteboard.value = null + isConnected.value = false + selectedTool.value = 'rectangle' + error.value = null + } + + async function addRectangle(rectangle: Rectangle) { + whiteboard.value?.rectangles.push(rectangle) + + try { + await whiteboardHubService.addRectangle(rectangle) + } catch (e: any) { + console.error('Failed to send rectangle', e) + } + } + + function selectTool(tool: ShapeTool) { + selectedTool.value = tool + } + + return { + whiteboard, + selectedTool, + isConnected, + isLoading, + error, + joinWhiteboard, + leaveWhiteboard, + addRectangle, + selectTool, + } +}) diff --git a/front/src/types/whiteboard.ts b/front/src/types/whiteboard.ts new file mode 100644 index 0000000..85818ab --- /dev/null +++ b/front/src/types/whiteboard.ts @@ -0,0 +1,42 @@ +export interface Position { + x: number + y: number +} + +export interface Shape { + id: string + ownerId: string + position: Position + color: string +} + +export interface Rectangle extends Shape { + endPosition: Position + borderThickness: number +} + +export interface Arrow extends Shape { + endPosition: Position + thickness: number +} + +export interface Line extends Shape { + endPosition: Position + thickness: number +} + +export interface TextShape extends Shape { + textValue: string + textSize: number +} + +export interface Whiteboard { + whiteboardId: string + ownerId: string + rectangles: Rectangle[] + arrows: Arrow[] + lines: Line[] + textShapes: TextShape[] +} + +export type ShapeTool = 'rectangle' | 'arrow' | 'line' | 'text' diff --git a/front/src/views/WhiteboardView.vue b/front/src/views/WhiteboardView.vue new file mode 100644 index 0000000..d9ff4f0 --- /dev/null +++ b/front/src/views/WhiteboardView.vue @@ -0,0 +1,45 @@ + + +