Whiteboard code in toolbar in Whiteboard view
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import {computed, ref} from 'vue'
|
||||||
import { useWhiteboardStore } from '@/stores/whiteboard.ts'
|
import { useWhiteboardStore } from '@/stores/whiteboard.ts'
|
||||||
|
import { useWhiteboardsStore } from "@/stores/whiteboards.ts";
|
||||||
import type { ShapeTool, Arrow, Line, Rectangle, TextShape } from '@/types/whiteboard.ts'
|
import type { ShapeTool, Arrow, Line, Rectangle, TextShape } from '@/types/whiteboard.ts'
|
||||||
|
|
||||||
const store = useWhiteboardStore()
|
const sessionStore = useWhiteboardStore()
|
||||||
|
const infoStore = useWhiteboardsStore()
|
||||||
const emit = defineEmits<{ leave: [] }>()
|
const emit = defineEmits<{ leave: [] }>()
|
||||||
|
|
||||||
const tools: { name: ShapeTool; label: string; icon: string; enabled: boolean }[] = [
|
const tools: { name: ShapeTool; label: string; icon: string; enabled: boolean }[] = [
|
||||||
@@ -16,43 +18,60 @@ const tools: { name: ShapeTool; label: string; icon: string; enabled: boolean }[
|
|||||||
|
|
||||||
const colors = ['#4f9dff', '#ff4f4f', '#4fff4f', '#ffff4f', '#ff4fff', '#ffffff', '#ff9f4f', '#4fffff']
|
const colors = ['#4f9dff', '#ff4f4f', '#4fff4f', '#ffff4f', '#ff4fff', '#ffffff', '#ff9f4f', '#4fffff']
|
||||||
|
|
||||||
const isReadOnly = computed(() => store.selectedTool === 'hand' && !!store.selectedShape)
|
const isReadOnly = computed(() => sessionStore.selectedTool === 'hand' && !!sessionStore.selectedShape)
|
||||||
|
|
||||||
const showProperties = computed(() => {
|
const showProperties = computed(() => {
|
||||||
if (['rectangle', 'arrow', 'line', 'text'].includes(store.selectedTool)) return true
|
if (['rectangle', 'arrow', 'line', 'text'].includes(sessionStore.selectedTool)) return true
|
||||||
if (store.selectedTool === 'hand' && store.selectedShape) return true
|
if (sessionStore.selectedTool === 'hand' && sessionStore.selectedShape) return true
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
const showThickness = computed(() => {
|
const showThickness = computed(() => {
|
||||||
if (['rectangle', 'arrow', 'line'].includes(store.selectedTool)) return true
|
if (['rectangle', 'arrow', 'line'].includes(sessionStore.selectedTool)) return true
|
||||||
if (isReadOnly.value && store.selectedShapeType && ['rectangle', 'arrow', 'line'].includes(store.selectedShapeType)) return true
|
if (isReadOnly.value && sessionStore.selectedShapeType && ['rectangle', 'arrow', 'line'].includes(sessionStore.selectedShapeType)) return true
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
const showTextSize = computed(() => {
|
const showTextSize = computed(() => {
|
||||||
if (store.selectedTool === 'text') return true
|
if (sessionStore.selectedTool === 'text') return true
|
||||||
if (isReadOnly.value && store.selectedShapeType === 'textShape') return true
|
if (isReadOnly.value && sessionStore.selectedShapeType === 'textShape') return true
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
const displayColor = computed(() => {
|
const displayColor = computed(() => {
|
||||||
if (isReadOnly.value && store.selectedShape) return store.selectedShape.color
|
if (isReadOnly.value && sessionStore.selectedShape) return sessionStore.selectedShape.color
|
||||||
return store.toolColor
|
return sessionStore.toolColor
|
||||||
})
|
})
|
||||||
|
|
||||||
const displayThickness = computed(() => {
|
const displayThickness = computed(() => {
|
||||||
if (isReadOnly.value && store.selectedShape) {
|
if (isReadOnly.value && sessionStore.selectedShape) {
|
||||||
if (store.selectedShapeType === 'rectangle') return (store.selectedShape as Rectangle).borderThickness
|
if (sessionStore.selectedShapeType === 'rectangle') return (sessionStore.selectedShape as Rectangle).borderThickness
|
||||||
return (store.selectedShape as Arrow | Line).thickness
|
return (sessionStore.selectedShape as Arrow | Line).thickness
|
||||||
}
|
}
|
||||||
return store.toolThickness
|
return sessionStore.toolThickness
|
||||||
})
|
})
|
||||||
|
|
||||||
const displayTextSize = computed(() => {
|
const displayTextSize = computed(() => {
|
||||||
if (isReadOnly.value && store.selectedShape) return (store.selectedShape as TextShape).textSize
|
if (isReadOnly.value && sessionStore.selectedShape) return (sessionStore.selectedShape as TextShape).textSize
|
||||||
return store.toolTextSize
|
return sessionStore.toolTextSize
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const showCopiedTooltip = ref(false)
|
||||||
|
|
||||||
|
const whiteboardCode = computed(() => infoStore.getCurrentWhiteboard()?.code || '')
|
||||||
|
|
||||||
|
const copyCodeToClipboard = async () => {
|
||||||
|
console.info(whiteboardCode.value)
|
||||||
|
if (!whiteboardCode.value) return
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(whiteboardCode.value)
|
||||||
|
showCopiedTooltip.value = true
|
||||||
|
setTimeout(() => showCopiedTooltip.value = false, 1500)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -62,10 +81,10 @@ const displayTextSize = computed(() => {
|
|||||||
v-for="tool in tools"
|
v-for="tool in tools"
|
||||||
:key="tool.name"
|
:key="tool.name"
|
||||||
class="tool-btn"
|
class="tool-btn"
|
||||||
:class="{ active: store.selectedTool === tool.name, disabled: !tool.enabled }"
|
:class="{ active: sessionStore.selectedTool === tool.name, disabled: !tool.enabled }"
|
||||||
:disabled="!tool.enabled"
|
:disabled="!tool.enabled"
|
||||||
:title="tool.enabled ? tool.label : `${tool.label} (coming soon)`"
|
:title="tool.enabled ? tool.label : `${tool.label} (coming soon)`"
|
||||||
@click="tool.enabled && store.selectTool(tool.name)"
|
@click="tool.enabled && sessionStore.selectTool(tool.name)"
|
||||||
>
|
>
|
||||||
{{ tool.icon }}
|
{{ tool.icon }}
|
||||||
</button>
|
</button>
|
||||||
@@ -85,9 +104,9 @@ const displayTextSize = computed(() => {
|
|||||||
v-for="c in colors"
|
v-for="c in colors"
|
||||||
:key="c"
|
:key="c"
|
||||||
class="color-swatch"
|
class="color-swatch"
|
||||||
:class="{ active: store.toolColor === c }"
|
:class="{ active: sessionStore.toolColor === c }"
|
||||||
:style="{ backgroundColor: c }"
|
:style="{ backgroundColor: c }"
|
||||||
@click="store.setToolColor(c)"
|
@click="sessionStore.setToolColor(c)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,8 +120,8 @@ const displayTextSize = computed(() => {
|
|||||||
min="1"
|
min="1"
|
||||||
max="10"
|
max="10"
|
||||||
step="1"
|
step="1"
|
||||||
:value="store.toolThickness"
|
:value="sessionStore.toolThickness"
|
||||||
@input="store.setToolThickness(Number(($event.target as HTMLInputElement).value))"
|
@input="sessionStore.setToolThickness(Number(($event.target as HTMLInputElement).value))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -115,13 +134,29 @@ const displayTextSize = computed(() => {
|
|||||||
min="12"
|
min="12"
|
||||||
max="72"
|
max="72"
|
||||||
step="2"
|
step="2"
|
||||||
:value="store.toolTextSize"
|
:value="sessionStore.toolTextSize"
|
||||||
@input="store.setToolTextSize(Number(($event.target as HTMLInputElement).value))"
|
@input="sessionStore.setToolTextSize(Number(($event.target as HTMLInputElement).value))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toolbar-footer">
|
<div class="toolbar-footer">
|
||||||
|
<div class="position-relative mb-2">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-outline-primary w-100"
|
||||||
|
@click="copyCodeToClipboard"
|
||||||
|
:title="whiteboardCode"
|
||||||
|
>
|
||||||
|
{{ whiteboardCode }}
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-if="showCopiedTooltip"
|
||||||
|
class="tooltip-custom position-absolute start-50 translate-middle-x"
|
||||||
|
>
|
||||||
|
Code copied to clipboard
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-outline-danger leave-btn"
|
class="btn btn-sm btn-outline-danger leave-btn"
|
||||||
title="Leave whiteboard"
|
title="Leave whiteboard"
|
||||||
@@ -227,4 +262,17 @@ const displayTextSize = computed(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip-custom {
|
||||||
|
bottom: 110%;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: center;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { whiteboardService } from '@/services/whiteboardService'
|
|||||||
export const useWhiteboardsStore = defineStore('whiteboards', () => {
|
export const useWhiteboardsStore = defineStore('whiteboards', () => {
|
||||||
const ownedWhiteboards = ref<Whiteboard[]>([])
|
const ownedWhiteboards = ref<Whiteboard[]>([])
|
||||||
const recentWhiteboards = ref<Whiteboard[]>([])
|
const recentWhiteboards = ref<Whiteboard[]>([])
|
||||||
|
const currentWhiteboard = ref<Whiteboard | null>(null)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
@@ -52,6 +53,36 @@ export const useWhiteboardsStore = defineStore('whiteboards', () => {
|
|||||||
return newWhiteboard.id;
|
return newWhiteboard.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteWhiteboard(id: string): Promise<void> {
|
||||||
|
isLoading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
await whiteboardService.deleteWhiteboard(id)
|
||||||
|
ownedWhiteboards.value = ownedWhiteboards.value.filter(wb => wb.id !== id)
|
||||||
|
} catch (err: any) {
|
||||||
|
error.value = err.message ?? 'Failed to delete whiteboard'
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentWhiteboard(): Whiteboard | null {
|
||||||
|
return currentWhiteboard.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectWhiteboard(whiteboardId: string) {
|
||||||
|
currentWhiteboard.value =
|
||||||
|
ownedWhiteboards.value.find(wb => wb.id === whiteboardId) ||
|
||||||
|
recentWhiteboards.value.find(wb => wb.id === whiteboardId) ||
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
function deselectWhiteboard() {
|
||||||
|
currentWhiteboard.value = null
|
||||||
|
}
|
||||||
|
|
||||||
function clearWhiteboards() {
|
function clearWhiteboards() {
|
||||||
ownedWhiteboards.value = []
|
ownedWhiteboards.value = []
|
||||||
recentWhiteboards.value = []
|
recentWhiteboards.value = []
|
||||||
@@ -65,6 +96,10 @@ export const useWhiteboardsStore = defineStore('whiteboards', () => {
|
|||||||
getWhiteboardHistory: getWhiteboardHistory,
|
getWhiteboardHistory: getWhiteboardHistory,
|
||||||
getRecentWhiteboards: getRecentWhiteboards,
|
getRecentWhiteboards: getRecentWhiteboards,
|
||||||
createNewWhiteboard: createNewWhiteboard,
|
createNewWhiteboard: createNewWhiteboard,
|
||||||
clearWhiteboards: clearWhiteboards
|
deleteWhiteboard: deleteWhiteboard,
|
||||||
|
getCurrentWhiteboard: getCurrentWhiteboard,
|
||||||
|
selectWhiteboard: selectWhiteboard,
|
||||||
|
deselectWhiteboard: deselectWhiteboard,
|
||||||
|
clearWhiteboards: clearWhiteboards,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export interface AuthResponse {
|
|||||||
export interface Whiteboard {
|
export interface Whiteboard {
|
||||||
id: string
|
id: string
|
||||||
ownerId: string
|
ownerId: string
|
||||||
|
code: string
|
||||||
title: string
|
title: string
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
deletedAt?: Date
|
deletedAt?: Date
|
||||||
|
|||||||
@@ -1,32 +1,42 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onUnmounted } from 'vue'
|
import { onMounted, onUnmounted, onBeforeMount, onBeforeUnmount } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useWhiteboardStore } from '@/stores/whiteboard.ts'
|
import { useWhiteboardStore } from '@/stores/whiteboard.ts'
|
||||||
|
import { useWhiteboardsStore } from "@/stores/whiteboards.ts";
|
||||||
import WhiteboardToolbar from '@/components/whiteboard/WhiteboardToolbar.vue'
|
import WhiteboardToolbar from '@/components/whiteboard/WhiteboardToolbar.vue'
|
||||||
import WhiteboardCanvas from '@/components/whiteboard/WhiteboardCanvas.vue'
|
import WhiteboardCanvas from '@/components/whiteboard/WhiteboardCanvas.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const store = useWhiteboardStore()
|
const sessionStore = useWhiteboardStore()
|
||||||
|
const infoStore = useWhiteboardsStore()
|
||||||
|
|
||||||
const whiteboardId = route.params.id as string
|
const whiteboardId = route.params.id as string
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
infoStore.selectWhiteboard(whiteboardId)
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
store.joinWhiteboard(whiteboardId)
|
sessionStore.joinWhiteboard(whiteboardId)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
infoStore.deselectWhiteboard()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
store.leaveWhiteboard()
|
sessionStore.leaveWhiteboard()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function handleLeave() {
|
async function handleLeave() {
|
||||||
await store.leaveWhiteboard()
|
await sessionStore.leaveWhiteboard()
|
||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="store.isLoading" class="d-flex flex-column justify-content-center align-items-center vh-100">
|
<div v-if="sessionStore.isLoading" class="d-flex flex-column justify-content-center align-items-center vh-100">
|
||||||
<div class="spinner-border text-primary mb-3" role="status">
|
<div class="spinner-border text-primary mb-3" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,9 +45,9 @@ async function handleLeave() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="store.error" class="d-flex justify-content-center align-items-center vh-100">
|
<div v-else-if="sessionStore.error" class="d-flex justify-content-center align-items-center vh-100">
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
{{ store.error }}
|
{{ sessionStore.error }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user