diff --git a/client/src/components/FloatingVideoWindow.vue b/client/src/components/FloatingVideoWindow.vue index b2cd01d..303822e 100644 --- a/client/src/components/FloatingVideoWindow.vue +++ b/client/src/components/FloatingVideoWindow.vue @@ -39,6 +39,13 @@ + + @@ -53,7 +60,8 @@ const selfVideoRef = ref(null); const windowStyle = computed(() => ({ left: `${chatStore.floatingVideoPosition.x}px`, - top: `${chatStore.floatingVideoPosition.y}px` + top: `${chatStore.floatingVideoPosition.y}px`, + width: `${chatStore.floatingVideoSize.width}px` })); watch( @@ -100,6 +108,29 @@ function startDrag(event) { window.addEventListener('mouseup', handleUp); } +function startResize(event) { + const startX = event.clientX; + const startY = event.clientY; + const startWidth = chatStore.floatingVideoSize.width; + const measuredHeight = event.currentTarget?.closest('.floating-video-window')?.offsetHeight || 0; + const startHeight = chatStore.floatingVideoSize.height || measuredHeight; + + const handleMove = (moveEvent) => { + chatStore.updateFloatingVideoSize({ + width: startWidth + (moveEvent.clientX - startX), + height: startHeight + (moveEvent.clientY - startY) + }); + }; + + const handleUp = () => { + window.removeEventListener('mousemove', handleMove); + window.removeEventListener('mouseup', handleUp); + }; + + window.addEventListener('mousemove', handleMove); + window.addEventListener('mouseup', handleUp); +} + onBeforeUnmount(() => { if (selfVideoRef.value) { selfVideoRef.value.srcObject = null; @@ -111,7 +142,6 @@ onBeforeUnmount(() => { .floating-video-window { position: fixed; z-index: 1300; - width: min(46vw, 720px); min-width: 340px; border-radius: 16px; border: 1px solid #cad5ce; @@ -224,9 +254,22 @@ onBeforeUnmount(() => { color: #ffffff; } +.floating-video-resize-handle { + position: absolute; + right: 6px; + bottom: 6px; + width: 18px; + height: 18px; + border: 0; + padding: 0; + cursor: nwse-resize; + background: + linear-gradient(135deg, transparent 0 42%, rgba(29, 106, 66, 0.2) 42% 52%, transparent 52% 62%, rgba(29, 106, 66, 0.45) 62% 72%, transparent 72% 82%, rgba(29, 106, 66, 0.75) 82% 92%, transparent 92% 100%); +} + @media (max-width: 860px) { .floating-video-window { - width: calc(100vw - 24px); + width: calc(100vw - 24px) !important; min-width: 0; left: 12px !important; top: 12px !important; diff --git a/client/src/components/VideoDock.vue b/client/src/components/VideoDock.vue index 84d741e..6c3129e 100644 --- a/client/src/components/VideoDock.vue +++ b/client/src/components/VideoDock.vue @@ -19,7 +19,15 @@ :key="session.callId" class="video-dock-card" > -
+
@@ -133,6 +141,10 @@ function statusLabel(status) { position: relative; } +.video-card-frame-clickable { + cursor: pointer; +} + .video-card-frame video { width: 100%; height: 100%; diff --git a/client/src/stores/chat.js b/client/src/stores/chat.js index b1f2774..6ee6cf0 100644 --- a/client/src/stores/chat.js +++ b/client/src/stores/chat.js @@ -42,6 +42,10 @@ function createDefaultFloatingPosition() { return { x: 24, y: 24 }; } +function createDefaultFloatingSize() { + return { width: 720, height: 0 }; +} + export const useChatStore = defineStore('chat', () => { const isLoggedIn = ref(false); const userName = ref(''); @@ -73,6 +77,7 @@ export const useChatStore = defineStore('chat', () => { const maxVideoConnectionsReached = ref(false); const foregroundVideoSessionId = ref(null); const floatingVideoPosition = ref(createDefaultFloatingPosition()); + const floatingVideoSize = ref(createDefaultFloatingSize()); const selfPreviewStream = shallowRef(null); const remoteStreams = ref({}); const selfMuted = ref(false); @@ -287,6 +292,7 @@ export const useChatStore = defineStore('chat', () => { selfMuted.value = false; selfCameraEnabled.value = true; floatingVideoPosition.value = createDefaultFloatingPosition(); + floatingVideoSize.value = createDefaultFloatingSize(); } async function ensureLocalPreviewStream() { @@ -1030,6 +1036,12 @@ export const useChatStore = defineStore('chat', () => { }; } + function updateFloatingVideoSize(size) { + const width = Math.max(340, Math.round(size.width || createDefaultFloatingSize().width)); + const height = Math.max(0, Math.round(size.height || 0)); + floatingVideoSize.value = { width, height }; + } + function toggleSelfMute() { selfMuted.value = !selfMuted.value; syncLocalTrackState(); @@ -1174,6 +1186,7 @@ export const useChatStore = defineStore('chat', () => { maxVideoConnectionsReached, foregroundVideoSessionId, floatingVideoPosition, + floatingVideoSize, selfPreviewStream, remoteStreams, selfMuted, @@ -1207,6 +1220,7 @@ export const useChatStore = defineStore('chat', () => { bringVideoSessionToFront, minimizeForegroundVideo, updateFloatingVideoPosition, + updateFloatingVideoSize, toggleSelfMute, toggleSelfCamera };