- useRdp.ts: JsonWsTunnel class extends Guacamole.Tunnel to bridge guacamole-common-js (expects raw protocol) with our JSON gateway (consistent with SSH/SFTP message envelope pattern). Parses length-prefixed Guacamole instructions, dispatches to Guacamole.Client. Handles mouse/keyboard input, clipboard send, and session lifecycle. - RdpCanvas.vue: full-size container that mounts the Guacamole display canvas. Calls useRdp().connectRdp() on mount, cleans up on unmount. Exposes sendClipboard() and disconnect() for toolbar integration. - RdpToolbar.vue: auto-hiding floating toolbar (3s idle timeout) with clipboard paste dialog, fullscreen toggle (HTML5 Fullscreen API), settings panel stub, and disconnect button. - SessionContainer.vue: renders RdpCanvas + RdpToolbar when session.protocol === 'rdp', replacing the Phase 3 placeholder. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
227 lines
6.3 KiB
Vue
227 lines
6.3 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { Monitor, Clipboard, Maximize2, Minimize2, X, Settings } from 'lucide-vue-next'
|
|
|
|
const props = defineProps<{
|
|
hostName: string
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
disconnect: []
|
|
sendClipboard: [text: string]
|
|
}>()
|
|
|
|
// Toolbar visibility — auto-hide after idle
|
|
const toolbarVisible = ref(true)
|
|
let hideTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
function showToolbar() {
|
|
toolbarVisible.value = true
|
|
if (hideTimer) clearTimeout(hideTimer)
|
|
hideTimer = setTimeout(() => {
|
|
toolbarVisible.value = false
|
|
}, 3000)
|
|
}
|
|
|
|
// Clipboard dialog
|
|
const clipboardOpen = ref(false)
|
|
const clipboardText = ref('')
|
|
|
|
function openClipboard() {
|
|
clipboardOpen.value = true
|
|
clipboardText.value = ''
|
|
}
|
|
|
|
function pasteClipboard() {
|
|
if (clipboardText.value) {
|
|
emit('sendClipboard', clipboardText.value)
|
|
}
|
|
clipboardOpen.value = false
|
|
}
|
|
|
|
// Fullscreen
|
|
const isFullscreen = ref(false)
|
|
|
|
async function toggleFullscreen() {
|
|
if (!document.fullscreenElement) {
|
|
await document.documentElement.requestFullscreen()
|
|
isFullscreen.value = true
|
|
} else {
|
|
await document.exitFullscreen()
|
|
isFullscreen.value = false
|
|
}
|
|
}
|
|
|
|
// Settings panel
|
|
const settingsOpen = ref(false)
|
|
|
|
// Disconnect
|
|
function disconnect() {
|
|
emit('disconnect')
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Floating toolbar — shows on mouse movement, auto-hides after 3s -->
|
|
<div
|
|
class="rdp-toolbar-wrapper"
|
|
@mousemove="showToolbar"
|
|
>
|
|
<Transition name="toolbar-slide">
|
|
<div
|
|
v-if="toolbarVisible"
|
|
class="rdp-toolbar"
|
|
>
|
|
<!-- Host name label -->
|
|
<div class="flex items-center gap-2 text-gray-300 text-sm font-medium min-w-0">
|
|
<Monitor class="w-4 h-4 text-wraith-400 shrink-0" />
|
|
<span class="truncate max-w-36">{{ props.hostName }}</span>
|
|
</div>
|
|
|
|
<div class="h-4 w-px bg-gray-600 mx-1" />
|
|
|
|
<!-- Clipboard -->
|
|
<button
|
|
class="toolbar-btn"
|
|
title="Send clipboard text"
|
|
@click="openClipboard"
|
|
>
|
|
<Clipboard class="w-4 h-4" />
|
|
</button>
|
|
|
|
<!-- Fullscreen toggle -->
|
|
<button
|
|
class="toolbar-btn"
|
|
:title="isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'"
|
|
@click="toggleFullscreen"
|
|
>
|
|
<Maximize2 v-if="!isFullscreen" class="w-4 h-4" />
|
|
<Minimize2 v-else class="w-4 h-4" />
|
|
</button>
|
|
|
|
<!-- Settings -->
|
|
<button
|
|
class="toolbar-btn"
|
|
title="RDP settings"
|
|
@click="settingsOpen = !settingsOpen"
|
|
>
|
|
<Settings class="w-4 h-4" />
|
|
</button>
|
|
|
|
<div class="h-4 w-px bg-gray-600 mx-1" />
|
|
|
|
<!-- Disconnect -->
|
|
<button
|
|
class="toolbar-btn toolbar-btn-danger"
|
|
title="Disconnect"
|
|
@click="disconnect"
|
|
>
|
|
<X class="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Clipboard Dialog -->
|
|
<Teleport to="body">
|
|
<div
|
|
v-if="clipboardOpen"
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
|
|
@click.self="clipboardOpen = false"
|
|
>
|
|
<div class="bg-gray-800 border border-gray-700 rounded-xl shadow-2xl w-full max-w-md p-6">
|
|
<h2 class="text-gray-100 font-semibold text-lg mb-4 flex items-center gap-2">
|
|
<Clipboard class="w-5 h-5 text-wraith-400" />
|
|
Send to clipboard
|
|
</h2>
|
|
<p class="text-gray-400 text-sm mb-3">
|
|
Type or paste text here. It will be sent to the remote session clipboard.
|
|
</p>
|
|
<textarea
|
|
v-model="clipboardText"
|
|
class="w-full bg-gray-900 border border-gray-600 rounded-lg text-gray-100 text-sm p-3 resize-none focus:outline-none focus:ring-2 focus:ring-wraith-500"
|
|
rows="5"
|
|
placeholder="Paste text to send..."
|
|
autofocus
|
|
/>
|
|
<div class="flex justify-end gap-3 mt-4">
|
|
<button
|
|
class="px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 text-gray-300 text-sm transition-colors"
|
|
@click="clipboardOpen = false"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
class="px-4 py-2 rounded-lg bg-wraith-600 hover:bg-wraith-500 text-white text-sm font-medium transition-colors"
|
|
:disabled="!clipboardText"
|
|
@click="pasteClipboard"
|
|
>
|
|
Send
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
|
|
<!-- Settings panel (minimal — expand in later tasks) -->
|
|
<Teleport to="body">
|
|
<div
|
|
v-if="settingsOpen"
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
|
|
@click.self="settingsOpen = false"
|
|
>
|
|
<div class="bg-gray-800 border border-gray-700 rounded-xl shadow-2xl w-full max-w-sm p-6">
|
|
<h2 class="text-gray-100 font-semibold text-lg mb-4 flex items-center gap-2">
|
|
<Settings class="w-5 h-5 text-wraith-400" />
|
|
RDP Settings
|
|
</h2>
|
|
<p class="text-gray-400 text-sm">
|
|
Advanced RDP settings (color depth, resize behavior) will be configurable here in a future release.
|
|
</p>
|
|
<div class="flex justify-end mt-6">
|
|
<button
|
|
class="px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 text-gray-300 text-sm transition-colors"
|
|
@click="settingsOpen = false"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.rdp-toolbar-wrapper {
|
|
@apply absolute inset-0 pointer-events-none z-20;
|
|
}
|
|
|
|
.rdp-toolbar {
|
|
@apply absolute top-4 left-1/2 -translate-x-1/2
|
|
flex items-center gap-1 px-3 py-1.5
|
|
bg-gray-900/90 backdrop-blur-sm
|
|
border border-gray-700 rounded-full shadow-xl
|
|
pointer-events-auto;
|
|
}
|
|
|
|
.toolbar-btn {
|
|
@apply p-1.5 rounded-full text-gray-400 hover:text-gray-100 hover:bg-gray-700
|
|
transition-colors duration-150;
|
|
}
|
|
|
|
.toolbar-btn-danger {
|
|
@apply hover:text-red-400 hover:bg-red-900/30;
|
|
}
|
|
|
|
/* Toolbar slide-down animation */
|
|
.toolbar-slide-enter-active,
|
|
.toolbar-slide-leave-active {
|
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
}
|
|
.toolbar-slide-enter-from,
|
|
.toolbar-slide-leave-to {
|
|
opacity: 0;
|
|
transform: translateX(-50%) translateY(-8px);
|
|
}
|
|
</style>
|