feat: Add persistent terminal with floating UI
- Server keeps PTY sessions alive on client disconnect - Output buffer replays history on reconnect - FloatingTerminal component accessible from any page - Responsive: floating window on desktop, fullscreen on mobile - macOS-style header with traffic light buttons
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { RouterView, useRoute, useRouter } from 'vue-router'
|
||||
import StatusBar from './components/StatusBar.vue'
|
||||
import Toolbar from './components/Toolbar.vue'
|
||||
import ComponentsDropdown from './components/ComponentsDropdown.vue'
|
||||
import FloatingTerminal from './components/FloatingTerminal.vue'
|
||||
import { initWebMCP } from './services/webmcp'
|
||||
import { initToolRegistry, activatePageTools, initToolsOnRefresh } from './services/toolRegistry'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const showTerminal = ref(false)
|
||||
|
||||
type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'projects' | 'project-canvas' | 'database' | 'source'
|
||||
type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'projects' | 'project-canvas' | 'database' | 'source' | 'terminal'
|
||||
|
||||
onMounted(async () => {
|
||||
// Initialize WebMCP connection
|
||||
@@ -49,6 +51,26 @@ watch(() => route.name, (newPage) => {
|
||||
</Transition>
|
||||
</RouterView>
|
||||
</main>
|
||||
|
||||
<!-- Floating Terminal Toggle Button -->
|
||||
<button
|
||||
class="terminal-fab"
|
||||
:class="{ active: showTerminal }"
|
||||
@click="showTerminal = !showTerminal"
|
||||
title="Toggle Terminal"
|
||||
>
|
||||
<svg v-if="!showTerminal" xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="4 17 10 11 4 5"></polyline>
|
||||
<line x1="12" y1="19" x2="20" y2="19"></line>
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Floating Terminal -->
|
||||
<FloatingTerminal v-model="showTerminal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -103,4 +125,53 @@ watch(() => route.name, (newPage) => {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
|
||||
/* Terminal FAB */
|
||||
.terminal-fab {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 9998;
|
||||
}
|
||||
|
||||
.terminal-fab:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 12px 32px rgba(99, 102, 241, 0.5);
|
||||
}
|
||||
|
||||
.terminal-fab.active {
|
||||
background: #ef4444;
|
||||
box-shadow: 0 8px 24px rgba(239, 68, 68, 0.4);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.terminal-fab.active:hover {
|
||||
box-shadow: 0 12px 32px rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.terminal-fab {
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
.terminal-fab.active {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user