Compare commits

...
Author SHA1 Message Date
bincxz 2af4fd0511 fix: use compact dock position menu 2026-09-28 22:52:04 +08:00
bincxz 8b722a996e fix: expose all side panel dock positions 2026-09-28 22:44:51 +08:00
8 changed files with 70 additions and 38 deletions

No files matched your search

+4
View File
@@ -602,6 +602,10 @@ export const enAiMessages: Messages = {
'terminal.layer.movePanelLeft': 'Move panel to left',
'terminal.layer.movePanelRight': 'Move panel to right',
'terminal.layer.movePanelBottom': 'Move panel to bottom',
'terminal.layer.choosePanelPosition': 'Change position',
'terminal.layer.dockLeft': 'Left',
'terminal.layer.dockRight': 'Right',
'terminal.layer.dockBottom': 'Bottom',
'terminal.layer.closePanel': 'Close panel',
'terminal.layer.closePane': 'Close split',
'terminal.layer.saveLayoutAsDefault': 'Save layout as default',
+4
View File
@@ -593,6 +593,10 @@ export const esAiMessages: Messages = {
'terminal.layer.movePanelLeft': 'Mover panel a la izquierda',
'terminal.layer.movePanelRight': 'Mover panel a la derecha',
'terminal.layer.movePanelBottom': 'Mover panel a la parte inferior',
'terminal.layer.choosePanelPosition': 'Cambiar posición',
'terminal.layer.dockLeft': 'Izquierda',
'terminal.layer.dockRight': 'Derecha',
'terminal.layer.dockBottom': 'Abajo',
'terminal.layer.closePanel': 'Cerrar panel',
'terminal.layer.closePane': 'Cerrar división',
'terminal.layer.resizeSplit': 'Redimensionar división',
+4
View File
@@ -561,6 +561,10 @@ export const ruAiMessages: Messages = {
'terminal.layer.movePanelLeft': 'Переместить панель влево',
'terminal.layer.movePanelRight': 'Переместить панель вправо',
'terminal.layer.movePanelBottom': 'Переместить панель вниз',
'terminal.layer.choosePanelPosition': 'Изменить положение',
'terminal.layer.dockLeft': 'Слева',
'terminal.layer.dockRight': 'Справа',
'terminal.layer.dockBottom': 'Снизу',
'terminal.layer.closePanel': 'Закрыть панель',
'terminal.layer.closePane': 'Закрыть область',
'terminal.layer.resizeSplit': 'Изменить размер области',
+4
View File
@@ -602,6 +602,10 @@ export const zhCNAiMessages: Messages = {
'terminal.layer.movePanelLeft': '面板移至左侧',
'terminal.layer.movePanelRight': '面板移至右侧',
'terminal.layer.movePanelBottom': '面板移至底部',
'terminal.layer.choosePanelPosition': '改变位置',
'terminal.layer.dockLeft': '左侧',
'terminal.layer.dockRight': '右侧',
'terminal.layer.dockBottom': '底部',
'terminal.layer.closePanel': '关闭面板',
'terminal.layer.closePane': '关闭分屏',
'terminal.layer.saveLayoutAsDefault': '将布局保存为默认',
+4
View File
@@ -592,6 +592,10 @@ export const zhTWAiMessages: Messages = {
'terminal.layer.movePanelLeft': '面板移至左側',
'terminal.layer.movePanelRight': '面板移至右側',
'terminal.layer.movePanelBottom': '面板移至底部',
'terminal.layer.choosePanelPosition': '變更位置',
'terminal.layer.dockLeft': '左側',
'terminal.layer.dockRight': '右側',
'terminal.layer.dockBottom': '底部',
'terminal.layer.closePanel': '關閉面板',
'terminal.layer.closePane': '關閉分割',
'terminal.layer.saveLayoutAsDefault': '將佈局儲存為預設',
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Activity, FolderTree, History, Maximize2, MessageSquare, Minimize2, NotebookText, Palette, PanelBottom, PanelLeft, PanelRight, Play, Save, SplitSquareHorizontal, SplitSquareVertical, X } from 'lucide-react';
import { Activity, FolderTree, History, Maximize2, MessageSquare, Minimize2, NotebookText, Palette, PanelsTopLeft, Play, Save, SplitSquareHorizontal, SplitSquareVertical, X } from 'lucide-react';
import {
buildSidePanelChromeThemeFromTerminalTheme,
buildTerminalSidePanelCssVars,
@@ -7,6 +7,7 @@ import {
import { injectTerminalLayerChromeSurfaceVars } from '../../infrastructure/theme/terminalAppearanceVars';
import React, { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
import { createPortal } from 'react-dom';
import * as SelectPrimitive from '@radix-ui/react-select';
import { useActiveTabId } from '../../application/state/activeTabStore';
import {
@@ -41,16 +42,17 @@ import {
} from '../ui/toolbar-item-layout';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from '../ui/popover';
import { Select, SelectContent, SelectItem } from '../ui/select';
import type { SidePanelTab } from './TerminalLayerSupport';
import {
MAX_SIDE_PANEL_PANES,
canSplitSidePanelPaneAtSize,
collectSidePanelPanes,
cycleSidePanelDockPosition,
getFocusedSidePanelPane,
getSidePanelNodeMinimumPixels,
getSidePanelSplitResizeBounds,
SIDE_PANEL_SPLIT_DIVIDER_PIXELS,
SIDE_PANEL_DOCK_POSITIONS,
type SidePanelDockPosition,
type SidePanelLayout,
type SidePanelLayoutNode,
@@ -491,6 +493,46 @@ function SidePanelSplitMenu({
);
}
function SidePanelDockSelect({
position,
onSelect,
t,
buttonColor,
}: {
position: SidePanelDockPosition;
onSelect: (position: SidePanelDockPosition) => void;
t: (key: string) => string;
buttonColor: string;
}) {
const labels = {
left: 'terminal.layer.dockLeft',
right: 'terminal.layer.dockRight',
bottom: 'terminal.layer.dockBottom',
};
return (
<Select value={position} onValueChange={(value) => onSelect(value as SidePanelDockPosition)}>
<Tooltip>
<TooltipTrigger asChild>
<SelectPrimitive.Trigger
className="grid h-7 w-7 shrink-0 place-items-center rounded-md bg-transparent p-0 hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
style={{ color: buttonColor }}
aria-label={t('terminal.layer.choosePanelPosition')}
>
<PanelsTopLeft size={15} />
</SelectPrimitive.Trigger>
</TooltipTrigger>
<TooltipContent side="bottom">{t('terminal.layer.choosePanelPosition')}</TooltipContent>
</Tooltip>
<SelectContent align="end" side="bottom" className="min-w-[7rem]" hideScrollButtons>
{SIDE_PANEL_DOCK_POSITIONS.map((dock) => (
<SelectItem key={dock} value={dock}>{t(labels[dock])}</SelectItem>
))}
</SelectContent>
</Select>
);
}
export function getTerminalSidePanelShellWidth({
activeSidePanelTab,
forceHideAiShell,
@@ -1393,28 +1435,12 @@ function TerminalLayerSidePanelInner({ ctx }: { ctx: SidePanelContext }) {
{t('terminal.layer.saveLayoutAsDefault')}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Btn
variant="ghost"
size="icon"
className="h-7 w-7 rounded-md p-0 hover:bg-transparent"
style={{ color: sidePanelTheme.mutedFg }}
onClick={() => setSidePanelPosition((p: SidePanelDockPosition) => cycleSidePanelDockPosition(p))}
>
{sidePanelPosition === 'left' && <PanelRight size={15} />}
{sidePanelPosition === 'right' && <PanelBottom size={15} />}
{sidePanelPosition === 'bottom' && <PanelLeft size={15} />}
</Btn>
</TooltipTrigger>
<TooltipContent side="bottom">
{sidePanelPosition === 'left'
? t('terminal.layer.movePanelRight')
: sidePanelPosition === 'right'
? t('terminal.layer.movePanelBottom')
: t('terminal.layer.movePanelLeft')}
</TooltipContent>
</Tooltip>
<SidePanelDockSelect
position={sidePanelPosition}
onSelect={setSidePanelPosition}
t={t}
buttonColor={sidePanelTheme.mutedFg}
/>
<Tooltip>
<TooltipTrigger asChild>
<Btn
-7
View File
@@ -7,7 +7,6 @@ import {
closeSidePanelPane,
collectSidePanelPanes,
createSidePanelLayout,
cycleSidePanelDockPosition,
focusSidePanelPane,
getSidePanelNodeMinimumPixels,
getSidePanelSplitResizeBounds,
@@ -169,12 +168,6 @@ test('the pane limit rejects additional splits without changing the layout', ()
assert.equal(duplicateAttempt.focusedPaneId, 'pane-0');
});
test('dock position cycles through left, right and bottom', () => {
assert.equal(cycleSidePanelDockPosition('left'), 'right');
assert.equal(cycleSidePanelDockPosition('right'), 'bottom');
assert.equal(cycleSidePanelDockPosition('bottom'), 'left');
});
test('dock position validator accepts only known positions', () => {
assert.equal(isSidePanelDockPosition('bottom'), true);
assert.equal(isSidePanelDockPosition('top'), false);
-7
View File
@@ -262,10 +262,3 @@ export function isSidePanelDockPosition(value: unknown): value is SidePanelDockP
return typeof value === 'string'
&& (SIDE_PANEL_DOCK_POSITIONS as readonly string[]).includes(value);
}
export function cycleSidePanelDockPosition(
position: SidePanelDockPosition,
): SidePanelDockPosition {
const index = SIDE_PANEL_DOCK_POSITIONS.indexOf(position);
return SIDE_PANEL_DOCK_POSITIONS[(index + 1) % SIDE_PANEL_DOCK_POSITIONS.length];
}