This commit is contained in:
Baobhan Sith
2025-04-30 09:58:08 +08:00
parent 8845392eee
commit 95a0a52185
17 changed files with 83 additions and 133 deletions
@@ -3,8 +3,8 @@ import { ref, reactive, watch, computed, onMounted } from 'vue'; // 添加 onMou
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useProxiesStore, ProxyInfo } from '../stores/proxies.store'; import { useProxiesStore, ProxyInfo } from '../stores/proxies.store';
import { useTagsStore } from '../stores/tags.store'; // 引入标签 Store import { useTagsStore } from '../stores/tags.store';
import TagInput from './TagInput.vue'; // 导入新的 TagInput 组件 import TagInput from './TagInput.vue';
// 定义组件发出的事件 // 定义组件发出的事件
const emit = defineEmits(['close', 'proxy-added', 'proxy-updated']); const emit = defineEmits(['close', 'proxy-added', 'proxy-updated']);
@@ -1,23 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, nextTick, onMounted, onBeforeUnmount, defineExpose, computed } from 'vue'; // Import computed import { ref, watch, nextTick, onMounted, onBeforeUnmount, defineExpose, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia'; // Import storeToRefs import { storeToRefs } from 'pinia';
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // 导入 Store import { useFocusSwitcherStore } from '../stores/focusSwitcher.store';
import { useSettingsStore } from '../stores/settings.store'; // NEW: Import settings store import { useSettingsStore } from '../stores/settings.store';
import { useQuickCommandsStore } from '../stores/quickCommands.store'; // NEW: Import quick commands store import { useQuickCommandsStore } from '../stores/quickCommands.store';
import { useCommandHistoryStore } from '../stores/commandHistory.store'; // NEW: Import command history store import { useCommandHistoryStore } from '../stores/commandHistory.store';
// 假设你有一个图标库,例如 unplugin-icons 或类似库
// import SearchIcon from '~icons/mdi/magnify';
// import ArrowUpIcon from '~icons/mdi/arrow-up';
// import ArrowDownIcon from '~icons/mdi/arrow-down';
// import CloseIcon from '~icons/mdi/close';
const emit = defineEmits(['send-command', 'search', 'find-next', 'find-previous', 'close-search']); // 移除 open-focus-switcher-config 事件 const emit = defineEmits(['send-command', 'search', 'find-next', 'find-previous', 'close-search']);
const { t } = useI18n(); const { t } = useI18n();
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化 Store +++ const focusSwitcherStore = useFocusSwitcherStore();
const settingsStore = useSettingsStore(); // NEW: Instantiate settings store const settingsStore = useSettingsStore();
const quickCommandsStore = useQuickCommandsStore(); // NEW: Instantiate quick commands store const quickCommandsStore = useQuickCommandsStore();
const commandHistoryStore = useCommandHistoryStore(); // NEW: Instantiate command history store const commandHistoryStore = useCommandHistoryStore();
// Get reactive setting from store // Get reactive setting from store
const { commandInputSyncTarget } = storeToRefs(settingsStore); const { commandInputSyncTarget } = storeToRefs(settingsStore);
@@ -1,18 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, computed, ref, reactive, watch } from 'vue'; // 统一导入, 添加 watch import { onMounted, computed, ref, reactive, watch } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store'; // 引入 ConnectionInfo 类型 import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store';
import { useTagsStore } from '../stores/tags.store'; // 引入 Tags Store import { useTagsStore } from '../stores/tags.store';
const { t } = useI18n(); // 获取 t 函数 const { t } = useI18n();
const router = useRouter(); // 获取 router 实例 const router = useRouter();
const tagsStore = useTagsStore(); // 获取 Tags Store 实例 const tagsStore = useTagsStore();
// 使用 storeToRefs 来保持 state 属性的响应性
// 不再直接从 connectionsStore 获取 connections, isLoading, error
// const { connections, isLoading, error } = storeToRefs(connectionsStore);
const { tags: allTags, isLoading: isTagsLoading, error: tagsError } = storeToRefs(tagsStore); // 获取所有标签及其状态 const { tags: allTags, isLoading: isTagsLoading, error: tagsError } = storeToRefs(tagsStore);
// 定义 Props,接收筛选后的连接列表 // 定义 Props,接收筛选后的连接列表
const props = defineProps<{ const props = defineProps<{
@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useSessionStore } from '../stores/session.store'; // Import session store import { useSessionStore } from '../stores/session.store';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
// Removed unused imports: ref, onMounted, onUnmounted, watch, useSettingsStore
// Removed unused interfaces: PortInfo, DockerContainer, DockerStats (now in composable)
const { t } = useI18n(); const { t } = useI18n();
const sessionStore = useSessionStore(); const sessionStore = useSessionStore();
@@ -4,16 +4,15 @@ import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import MonacoEditor from './MonacoEditor.vue'; import MonacoEditor from './MonacoEditor.vue';
import FileEditorTabs from './FileEditorTabs.vue'; import FileEditorTabs from './FileEditorTabs.vue';
import { useFileEditorStore, type FileTab } from '../stores/fileEditor.store'; // 导入 FileTab 类型 import { useFileEditorStore, type FileTab } from '../stores/fileEditor.store';
import { useSettingsStore } from '../stores/settings.store'; import { useSettingsStore } from '../stores/settings.store';
import { useSessionStore } from '../stores/session.store'; // 导入 Session Store import { useSessionStore } from '../stores/session.store';
import type { EditorFileContent, SaveStatus } from '../types/sftp.types';
import { getLanguageFromFilename, getFilenameFromPath } from '../stores/fileEditor.store';
const { t } = useI18n(); const { t } = useI18n();
const fileEditorStore = useFileEditorStore(); const fileEditorStore = useFileEditorStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const sessionStore = useSessionStore(); // 实例化 Session Store const sessionStore = useSessionStore();
// --- 本地状态控制弹窗显示 --- // --- 本地状态控制弹窗显示 ---
const isVisible = ref(false); const isVisible = ref(false);
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import type { FileTab } from '../stores/fileEditor.store'; // FileTab import type { FileTab } from '../stores/fileEditor.store';
defineProps({ defineProps({
tabs: { tabs: {
@@ -1,31 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch, watchEffect, type PropType, readonly, defineExpose, shallowRef } from 'vue'; import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch, watchEffect, type PropType, readonly, defineExpose, shallowRef } from 'vue';
// debounce
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; // URL () import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia'; // storeToRefs import { storeToRefs } from 'pinia';
// SFTP Actions
import { createSftpActionsManager, type WebSocketDependencies } from '../composables/useSftpActions'; import { createSftpActionsManager, type WebSocketDependencies } from '../composables/useSftpActions';
import { useFileUploader } from '../composables/useFileUploader'; import { useFileUploader } from '../composables/useFileUploader';
// import { useFileEditor } from '../composables/useFileEditor'; // composable import { useFileEditorStore, type FileInfo } from '../stores/fileEditor.store';
import { useFileEditorStore, type FileInfo } from '../stores/fileEditor.store'; // Store FileInfo
import { useSessionStore } from '../stores/session.store'; import { useSessionStore } from '../stores/session.store';
import { useSettingsStore } from '../stores/settings.store'; // +++ Settings Store +++ import { useSettingsStore } from '../stores/settings.store';
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ Store +++ import { useFocusSwitcherStore } from '../stores/focusSwitcher.store';
import { useFileManagerContextMenu, type ClipboardState } from '../composables/file-manager/useFileManagerContextMenu'; // +++ Composable ClipboardState +++ import { useFileManagerContextMenu, type ClipboardState } from '../composables/file-manager/useFileManagerContextMenu';
import { useFileManagerSelection } from '../composables/file-manager/useFileManagerSelection'; // +++ Composable +++ import { useFileManagerSelection } from '../composables/file-manager/useFileManagerSelection';
import { useFileManagerDragAndDrop } from '../composables/file-manager/useFileManagerDragAndDrop'; // +++ Composable +++ import { useFileManagerDragAndDrop } from '../composables/file-manager/useFileManagerDragAndDrop';
import { useFileManagerKeyboardNavigation } from '../composables/file-manager/useFileManagerKeyboardNavigation'; // +++ Composable +++ import { useFileManagerKeyboardNavigation } from '../composables/file-manager/useFileManagerKeyboardNavigation';
// WebSocket composable 使
import FileUploadPopup from './FileUploadPopup.vue'; import FileUploadPopup from './FileUploadPopup.vue';
import FileManagerContextMenu from './FileManagerContextMenu.vue'; // +++ +++ import FileManagerContextMenu from './FileManagerContextMenu.vue';
// import FileEditorOverlay from './FileEditorOverlay.vue'; //
//
import type { FileListItem } from '../types/sftp.types'; import type { FileListItem } from '../types/sftp.types';
// websocket import type { WebSocketMessage } from '../types/websocket.types';
import type { WebSocketMessage } from '../types/websocket.types'; // WebSocketMessage
// SftpManagerInstance createSftpActionsManager
type SftpManagerInstance = ReturnType<typeof createSftpActionsManager>; type SftpManagerInstance = ReturnType<typeof createSftpActionsManager>;
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import type { UploadItem } from '../types/upload.types'; // import type { UploadItem } from '../types/upload.types';
const props = defineProps<{ const props = defineProps<{
uploads: Record<string, UploadItem>; // uploads: Record<string, UploadItem>; //
@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, reactive, type Ref } from 'vue'; // Ref import { ref, computed, watch, reactive, type Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import draggable from 'vuedraggable'; import draggable from 'vuedraggable';
import { useFocusSwitcherStore, type FocusableInput, type FocusItemConfig, type FocusSwitcherFullConfig } from '../stores/focusSwitcher.store'; // ++ ++ import { useFocusSwitcherStore, type FocusableInput, type FocusItemConfig, type FocusSwitcherFullConfig } from '../stores/focusSwitcher.store';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
// //
@@ -236,9 +236,6 @@ const clonePane = (paneName: PaneName): LayoutNode => {
// Handle updates from LayoutNodeEditor (for main layout) // Handle updates from LayoutNodeEditor (for main layout)
const handleNodeUpdate = (updatedNode: LayoutNode) => { const handleNodeUpdate = (updatedNode: LayoutNode) => {
console.log('[LayoutConfigurator] Received node update from editor:', updatedNode); console.log('[LayoutConfigurator] Received node update from editor:', updatedNode);
// Assuming the update is for the root node for simplicity
// v-model on LayoutNodeEditor might handle this, but explicit update is safer
// Update the local tree; isModified will react automatically
localLayoutTree.value = updatedNode; localLayoutTree.value = updatedNode;
}; };
@@ -278,11 +275,6 @@ const handleNodeRemove = (payload: { parentNodeId: string | undefined; nodeIndex
console.log('[LayoutConfigurator] Received node remove request:', payload); console.log('[LayoutConfigurator] Received node remove request:', payload);
if (payload.parentNodeId === undefined && payload.nodeIndex === 0) { if (payload.parentNodeId === undefined && payload.nodeIndex === 0) {
if (confirm(t('layoutConfigurator.confirmClearLayout', '确定要清空整个布局吗?所有面板将返回可用列表。'))) { // Keep default text for now if (confirm(t('layoutConfigurator.confirmClearLayout', '确定要清空整个布局吗?所有面板将返回可用列表。'))) { // Keep default text for now
// Add all panes from the tree back to available list before clearing - REMOVED, no longer needed
// const usedInTree = getMainLayoutUsedPaneNames(localLayoutTree.value);
// usedInTree.forEach(paneName => addPaneToAvailableList(paneName));
// Clear the tree
// Update the local tree; isModified will react automatically
localLayoutTree.value = null; localLayoutTree.value = null;
} }
} else if (payload.parentNodeId) { } else if (payload.parentNodeId) {
@@ -102,23 +102,8 @@ const handleChildUpdate = (updatedChildNode: LayoutNode, index: number) => {
// //
const handleChildRemove = (payload: { parentNodeId: string | undefined; nodeIndex: number }) => { const handleChildRemove = (payload: { parentNodeId: string | undefined; nodeIndex: number }) => {
// LayoutConfigurator // LayoutConfigurator
console.log(`[LayoutNodeEditor ${props.node.id}] Relaying removeNode event upwards:`, payload); // console.log(`[LayoutNodeEditor ${props.node.id}] Relaying removeNode event upwards:`, payload);
emit('removeNode', payload); emit('removeNode', payload);
/*
//
if (payload.parentNodeId === props.node.id && props.node.children) {
const newChildren = [...props.node.children];
newChildren.splice(payload.nodeIndex, 1);
//
//
// emit update:node LayoutConfigurator
emit('update:node', { ...props.node, children: newChildren });
} else {
//
emit('removeNode', payload);
}
*/
}; };
</script> </script>
@@ -194,28 +179,16 @@ const handleChildRemove = (payload: { parentNodeId: string | undefined; nodeInde
<style scoped> <style scoped>
.layout-node-editor { .layout-node-editor {
/* border: 1px solid var(--border-color);
margin: var(--base-margin);
padding: var(--base-margin);
position: relative;
background-color: var(--header-bg-color);
min-height: 60px;
display: flex;
flex-direction: column; */
} }
/* .node-type-container {
background-color: var(--app-bg-color);
}
.node-type-pane {
background-color: var(--app-bg-color);
} */
.node-controls { .node-controls {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background-color: var(--header-bg-color); /* Or a specific control bar background */ background-color: var(--header-bg-color);
padding: 3px var(--base-margin); padding: 3px var(--base-margin);
margin-bottom: var(--base-margin); margin-bottom: var(--base-margin);
font-size: 0.8em; font-size: 0.8em;
@@ -6,7 +6,7 @@
import { ref, onMounted, onBeforeUnmount, watch, defineExpose } from 'vue'; import { ref, onMounted, onBeforeUnmount, watch, defineExpose } from 'vue';
import * as monaco from 'monaco-editor'; import * as monaco from 'monaco-editor';
const localFontSize = ref(14); // <-- 14 const localFontSize = ref(14); // 14
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@@ -1,5 +1,5 @@
<template> <template>
<div class="p-0"> <!-- Remove padding from here, parent view provides it --> <div class="p-0">
<h2 class="text-xl font-semibold text-foreground mb-4 pb-2 border-b border-border"> <!-- Title styling --> <h2 class="text-xl font-semibold text-foreground mb-4 pb-2 border-b border-border"> <!-- Title styling -->
{{ $t('settings.notifications.title') }} {{ $t('settings.notifications.title') }}
</h2> </h2>
@@ -1,40 +1,40 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, watch, computed } from 'vue'; import { ref, reactive, onMounted, watch, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAppearanceStore } from '../stores/appearance.store'; // 使 store import { useAppearanceStore } from '../stores/appearance.store';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import type { ITheme } from 'xterm'; import type { ITheme } from 'xterm';
import type { TerminalTheme } from '../types/terminal-theme.types'; // import type { TerminalTheme } from '../types/terminal-theme.types';
import { defaultXtermTheme } from '../features/appearance/config/default-themes'; // import { defaultXtermTheme } from '../features/appearance/config/default-themes';
const { t } = useI18n(); const { t } = useI18n();
const appearanceStore = useAppearanceStore(); const appearanceStore = useAppearanceStore();
const { const {
appearanceSettings, // <-- ref appearanceSettings,
currentUiTheme, currentUiTheme,
// currentTerminalTheme, //
activeTerminalThemeId, // number | null | undefined activeTerminalThemeId,
allTerminalThemes, // 使 allTerminalThemes,
currentTerminalFontFamily, currentTerminalFontFamily,
currentTerminalFontSize, currentTerminalFontSize,
currentEditorFontSize, // <-- currentEditorFontSize,
pageBackgroundImage, pageBackgroundImage,
// pageBackgroundOpacity, // Removed
terminalBackgroundImage, terminalBackgroundImage,
// terminalBackgroundOpacity, // Removed
isTerminalBackgroundEnabled, // <-- isTerminalBackgroundEnabled,
} = storeToRefs(appearanceStore); } = storeToRefs(appearanceStore);
// --- ---
const editableUiTheme = ref<Record<string, string>>({}); const editableUiTheme = ref<Record<string, string>>({});
const editableTerminalFontFamily = ref(''); const editableTerminalFontFamily = ref('');
const editableTerminalFontSize = ref(14); const editableTerminalFontSize = ref(14);
const editableEditorFontSize = ref(14); // <-- const editableEditorFontSize = ref(14);
// const editablePageBackgroundOpacity = ref(1.0); // Removed
// const editableTerminalBackgroundOpacity = ref(1.0); // Removed
const editableUiThemeString = ref(''); // textarea const editableUiThemeString = ref('');
const themeParseError = ref<string | null>(null); // JSON const themeParseError = ref<string | null>(null);
const localTerminalBackgroundEnabled = ref(true); // <-- const localTerminalBackgroundEnabled = ref(true);
// //
const isEditingTheme = ref(false); // const isEditingTheme = ref(false); //
@@ -1,15 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'; import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
import { ITheme } from 'xterm'; import { Terminal, ITerminalAddon, IDisposable } from 'xterm';
import { Terminal, ITerminalAddon, IDisposable } from 'xterm'; // +++ ITerminalAddon IDisposable +++ import { useAppearanceStore } from '../stores/appearance.store';
import { useAppearanceStore } from '../stores/appearance.store'; // store import { useSettingsStore } from '../stores/settings.store';
import { useSettingsStore } from '../stores/settings.store'; // +++ store +++ import { storeToRefs } from 'pinia';
import { storeToRefs } from 'pinia'; // storeToRefs
import { FitAddon } from 'xterm-addon-fit'; import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links'; import { WebLinksAddon } from 'xterm-addon-web-links';
import { SearchAddon, type ISearchOptions } from '@xterm/addon-search'; // *** *** import { SearchAddon, type ISearchOptions } from '@xterm/addon-search';
import 'xterm/css/xterm.css'; // xterm import 'xterm/css/xterm.css';
// *** CSS ***
// props emits // props emits
const props = defineProps<{ const props = defineProps<{
@@ -7,7 +7,7 @@ import WorkspaceConnectionListComponent from './WorkspaceConnectionList.vue';
import { useSessionStore } from '../stores/session.store'; import { useSessionStore } from '../stores/session.store';
import { useConnectionsStore, type ConnectionInfo } from '../stores/connections.store'; import { useConnectionsStore, type ConnectionInfo } from '../stores/connections.store';
import { useLayoutStore, type PaneName } from '../stores/layout.store'; import { useLayoutStore, type PaneName } from '../stores/layout.store';
//
import type { SessionTabInfoWithStatus } from '../stores/session.store'; import type { SessionTabInfoWithStatus } from '../stores/session.store';
@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, defineExpose, watch, nextTick } from 'vue'; import { ref, computed, onMounted, onBeforeUnmount, defineExpose, watch, nextTick } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
// import { useRouter } from 'vue-router'; // router
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
// import RemoteDesktopModal from './RemoteDesktopModal.vue'; // --- RDP ---
import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store'; import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store';
import { useTagsStore, TagInfo } from '../stores/tags.store'; import { useTagsStore, TagInfo } from '../stores/tags.store';
import { useSessionStore } from '../stores/session.store'; // session store import { useSessionStore } from '../stores/session.store';
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ Store +++ import { useFocusSwitcherStore } from '../stores/focusSwitcher.store';
// //
const emit = defineEmits([ const emit = defineEmits([
@@ -15,8 +15,7 @@ const emit = defineEmits([
// 'open-new-session', // - () // 'open-new-session', // - ()
'request-add-connection', // - 'request-add-connection', // -
'request-edit-connection' // - 'request-edit-connection' // -
// --- RDP ---
// 'request-rdp-modal'
]); ]);
const { t } = useI18n(); const { t } = useI18n();