Polish tmux session modal (#1412)
Some checks failed
build-packages / ${{ needs.dedupe.outputs.skip_heavy_ci == 'true' && 'deduped build-linux-x64' || 'build-linux-x64' }} (push) Has been cancelled
build-packages / ${{ needs.dedupe.outputs.skip_heavy_ci == 'true' && 'deduped build-linux-arm64' || 'build-linux-arm64' }} (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / dedupe push run (push) Has been cancelled
build-packages / dedupe result (push) Has been cancelled
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
Some checks failed
build-packages / ${{ needs.dedupe.outputs.skip_heavy_ci == 'true' && 'deduped build-linux-x64' || 'build-linux-x64' }} (push) Has been cancelled
build-packages / ${{ needs.dedupe.outputs.skip_heavy_ci == 'true' && 'deduped build-linux-arm64' || 'build-linux-arm64' }} (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / dedupe push run (push) Has been cancelled
build-packages / dedupe result (push) Has been cancelled
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
This commit is contained in:
@@ -19,14 +19,25 @@ const DEFAULT_HEIGHT = 120;
|
||||
const MIN_HEIGHT = 80;
|
||||
const MAX_HEIGHT = 520;
|
||||
|
||||
function clampHeight(height: number): number {
|
||||
return Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, height));
|
||||
function clampHeight(height: number, minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT): number {
|
||||
return Math.max(minHeight, Math.min(maxHeight, height));
|
||||
}
|
||||
|
||||
function readStoredHeight(): number {
|
||||
function readStoredHeight({
|
||||
defaultHeight,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
persistHeight,
|
||||
}: {
|
||||
defaultHeight: number;
|
||||
minHeight: number;
|
||||
maxHeight: number;
|
||||
persistHeight: boolean;
|
||||
}): number {
|
||||
if (!persistHeight) return clampHeight(defaultHeight, minHeight, maxHeight);
|
||||
const stored = localStorageAdapter.readNumber(STORAGE_KEY_SNIPPET_SCRIPT_EDITOR_HEIGHT);
|
||||
if (stored === null) return DEFAULT_HEIGHT;
|
||||
return clampHeight(stored);
|
||||
if (stored === null) return clampHeight(defaultHeight, minHeight, maxHeight);
|
||||
return clampHeight(stored, minHeight, maxHeight);
|
||||
}
|
||||
|
||||
const editorFillClass =
|
||||
@@ -39,6 +50,10 @@ export interface SnippetScriptEditorProps {
|
||||
id?: string;
|
||||
/** Shown on the same row as the expand button (e.g. "Script *"). */
|
||||
label?: string;
|
||||
defaultHeight?: number;
|
||||
minHeight?: number;
|
||||
maxHeight?: number;
|
||||
persistHeight?: boolean;
|
||||
}
|
||||
|
||||
export const SnippetScriptEditor: React.FC<SnippetScriptEditorProps> = ({
|
||||
@@ -47,9 +62,18 @@ export const SnippetScriptEditor: React.FC<SnippetScriptEditorProps> = ({
|
||||
placeholder,
|
||||
id,
|
||||
label,
|
||||
defaultHeight = DEFAULT_HEIGHT,
|
||||
minHeight = MIN_HEIGHT,
|
||||
maxHeight = MAX_HEIGHT,
|
||||
persistHeight = true,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const [height, setHeight] = useState(readStoredHeight);
|
||||
const [height, setHeight] = useState(() => readStoredHeight({
|
||||
defaultHeight,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
persistHeight,
|
||||
}));
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const dragRef = useRef<{ startY: number; startHeight: number } | null>(null);
|
||||
const heightRef = useRef(height);
|
||||
@@ -66,10 +90,10 @@ export const SnippetScriptEditor: React.FC<SnippetScriptEditorProps> = ({
|
||||
const onMove = (e: MouseEvent) => {
|
||||
if (!dragRef.current) return;
|
||||
const delta = e.clientY - dragRef.current.startY;
|
||||
setHeight(clampHeight(dragRef.current.startHeight + delta));
|
||||
setHeight(clampHeight(dragRef.current.startHeight + delta, minHeight, maxHeight));
|
||||
};
|
||||
const onUp = () => {
|
||||
if (dragRef.current) {
|
||||
if (dragRef.current && persistHeight) {
|
||||
localStorageAdapter.writeNumber(
|
||||
STORAGE_KEY_SNIPPET_SCRIPT_EDITOR_HEIGHT,
|
||||
heightRef.current,
|
||||
@@ -87,7 +111,7 @@ export const SnippetScriptEditor: React.FC<SnippetScriptEditorProps> = ({
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
};
|
||||
}, []);
|
||||
}, [maxHeight, minHeight, persistHeight]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
22
components/systemManager/TmuxNewSessionModal.test.ts
Normal file
22
components/systemManager/TmuxNewSessionModal.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
const modalSource = readFileSync(new URL("./TmuxNewSessionModal.tsx", import.meta.url), "utf8");
|
||||
const editorSource = readFileSync(new URL("../snippets/SnippetScriptEditor.tsx", import.meta.url), "utf8");
|
||||
|
||||
test("tmux new session modal is only modestly wider than the default dialog", () => {
|
||||
assert.match(modalSource, /w-\[min\(92vw,560px\)\]/);
|
||||
assert.match(modalSource, /max-w-none/);
|
||||
assert.doesNotMatch(modalSource, /bg-background\/95/);
|
||||
assert.doesNotMatch(modalSource, /bg-muted\/10/);
|
||||
assert.doesNotMatch(modalSource, /border-b border-border\/60/);
|
||||
assert.doesNotMatch(modalSource, /border-t border-border\/60/);
|
||||
});
|
||||
|
||||
test("tmux command editor does not inherit the global snippet editor height", () => {
|
||||
assert.match(modalSource, /defaultHeight=\{150\}/);
|
||||
assert.match(modalSource, /maxHeight=\{260\}/);
|
||||
assert.match(modalSource, /persistHeight=\{false\}/);
|
||||
assert.match(editorSource, /persistHeight\?: boolean/);
|
||||
});
|
||||
@@ -100,10 +100,10 @@ export const TmuxNewSessionModal = memo(function TmuxNewSessionModal({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className="flex max-h-[min(90vh,720px)] max-w-md flex-col overflow-hidden"
|
||||
className="flex max-h-[min(88vh,680px)] w-[min(92vw,560px)] max-w-none flex-col overflow-hidden"
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<DialogHeader className="shrink-0">
|
||||
<DialogHeader className="shrink-0 pr-8">
|
||||
<DialogTitle>{t('systemManager.tmux.newSessionTitle')}</DialogTitle>
|
||||
<DialogDescription>{t('systemManager.tmux.newSessionDesc')}</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -146,6 +146,9 @@ export const TmuxNewSessionModal = memo(function TmuxNewSessionModal({
|
||||
value={command}
|
||||
onChange={handleCommandChange}
|
||||
placeholder={t('systemManager.tmux.newSessionCommandPlaceholder')}
|
||||
defaultHeight={150}
|
||||
maxHeight={260}
|
||||
persistHeight={false}
|
||||
/>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{t('systemManager.tmux.newSessionCommandHint')}
|
||||
@@ -158,7 +161,7 @@ export const TmuxNewSessionModal = memo(function TmuxNewSessionModal({
|
||||
selectedId={selectedSnippetId}
|
||||
onSelect={handleSnippetSelect}
|
||||
showTitle={false}
|
||||
className="min-h-[280px]"
|
||||
className="h-[240px] min-h-[240px]"
|
||||
/>
|
||||
{selectedSnippet && (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user