feat: implement terminal input broadcasting across multiple active panes
This commit is contained in:
+32
-1
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { Folder, Pencil, Trash2 } from "lucide-vue-next";
|
||||
import { Folder, Pencil, Trash2, Radio } from "lucide-vue-next";
|
||||
import {
|
||||
api,
|
||||
type HostRow,
|
||||
@@ -37,6 +37,22 @@ const tabs = ref<{ id: string; hostId: number; label: string }[]>([]);
|
||||
const activePanes = ref<string[]>([]);
|
||||
const draggedTabId = ref<string | null>(null);
|
||||
|
||||
const broadcastMode = ref(false);
|
||||
const tabRefs = ref<Record<string, any>>({});
|
||||
function setTabRef(el: any, id: string) {
|
||||
if (el) tabRefs.value[id] = el;
|
||||
else delete tabRefs.value[id];
|
||||
}
|
||||
|
||||
function handleBroadcast(data: string, sourceId: string) {
|
||||
if (!broadcastMode.value) return;
|
||||
for (const paneId of activePanes.value) {
|
||||
if (paneId !== sourceId && tabRefs.value[paneId]) {
|
||||
tabRefs.value[paneId].sendData(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onTabDragStart(id: string) {
|
||||
draggedTabId.value = id;
|
||||
}
|
||||
@@ -744,6 +760,19 @@ async function deleteIdentityRow(id: number) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
v-if="activePanes.length > 1"
|
||||
type="button"
|
||||
class="hidden rounded-lg px-3 py-1.5 text-xs md:inline-flex border transition-colors"
|
||||
:class="broadcastMode ? 'border-accent bg-accent/10 text-accent' : 'border-slate-800 text-slate-400 hover:border-slate-700 hover:text-white'"
|
||||
@click="broadcastMode = !broadcastMode"
|
||||
title="Broadcast input to all visible terminals"
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<Radio class="h-3.5 w-3.5" />
|
||||
Broadcast
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="hidden rounded-lg px-3 py-1.5 text-xs md:inline-flex"
|
||||
@@ -1067,9 +1096,11 @@ async function deleteIdentityRow(id: number) {
|
||||
class="flex-1 min-w-0 h-full"
|
||||
>
|
||||
<TabContent
|
||||
:ref="(el) => setTabRef(el, t.id)"
|
||||
:host-id="t.hostId"
|
||||
:visible="activePanes.includes(t.id)"
|
||||
:show-sftp="showSftpPanel"
|
||||
@broadcast-data="(data: string) => handleBroadcast(data, t.id)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,6 +17,18 @@ const props = defineProps<{
|
||||
showSftp: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'broadcast-data', data: string): void;
|
||||
}>();
|
||||
|
||||
defineExpose({
|
||||
sendData: (data: string) => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(new TextEncoder().encode(data));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const termEl = ref<HTMLElement | null>(null);
|
||||
const status = ref("Connecting…");
|
||||
const connId = ref<string | null>(null);
|
||||
@@ -97,6 +109,7 @@ onMounted(async () => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(new TextEncoder().encode(data));
|
||||
}
|
||||
emit("broadcast-data", data);
|
||||
});
|
||||
|
||||
term.onResize(({ cols, rows }) => {
|
||||
|
||||
Reference in New Issue
Block a user