Handle enter button (#481)
* Update Crowdin configuration file * Update Crowdin configuration file * Update Linux Portable section with AUR link (#474) * fix: file manager incorrectly decoding/encoding when editing files (#476) * fix: electron build errors and skip macos job * fix: testflight submit failure * fix: made submit job match build type * fix: resolve Vite build warnings for mixed static/dynamic imports (#473) * Update Crowdin configuration file * Update Crowdin configuration file * fix: resolve Vite build warnings for mixed static/dynamic imports - Convert all dynamic imports of main-axios.ts to static imports (10 files) - Convert all dynamic imports of sonner to static imports (4 files) - Add manual chunking configuration to vite.config.ts for better bundle splitting - react-vendor: React and React DOM - ui-vendor: Radix UI, lucide-react, clsx, tailwind-merge - monaco: Monaco Editor - codemirror: CodeMirror and related packages - Increase chunkSizeWarningLimit to 1000kB This resolves Vite warnings about mixed import strategies preventing proper code-splitting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> Co-authored-by: Termix CI <ci@termix.dev> Co-authored-by: Claude <noreply@anthropic.com> * fix: file manager incorrectly decoding/encoding when editing files (made base64/utf8 dependent) --------- Co-authored-by: Jefferson Nunn <89030989+jeffersonwarrior@users.noreply.github.com> Co-authored-by: Termix CI <ci@termix.dev> Co-authored-by: Claude <noreply@anthropic.com> * fix: build error on docker (#477) * fix: electron build errors and skip macos job * fix: testflight submit failure * fix: made submit job match build type * fix: resolve Vite build warnings for mixed static/dynamic imports (#473) * Update Crowdin configuration file * Update Crowdin configuration file * fix: resolve Vite build warnings for mixed static/dynamic imports - Convert all dynamic imports of main-axios.ts to static imports (10 files) - Convert all dynamic imports of sonner to static imports (4 files) - Add manual chunking configuration to vite.config.ts for better bundle splitting - react-vendor: React and React DOM - ui-vendor: Radix UI, lucide-react, clsx, tailwind-merge - monaco: Monaco Editor - codemirror: CodeMirror and related packages - Increase chunkSizeWarningLimit to 1000kB This resolves Vite warnings about mixed import strategies preventing proper code-splitting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> Co-authored-by: Termix CI <ci@termix.dev> Co-authored-by: Claude <noreply@anthropic.com> * fix: file manager incorrectly decoding/encoding when editing files (made base64/utf8 dependent) * fix: build error on docker --------- Co-authored-by: Jefferson Nunn <89030989+jeffersonwarrior@users.noreply.github.com> Co-authored-by: Termix CI <ci@termix.dev> Co-authored-by: Claude <noreply@anthropic.com> * Increase max old space size for npm builds * Increase Node.js memory limit in Dockerfile * Remove NODE_OPTIONS from build commands in Dockerfile * Change runner to blacksmith-4vcpu-ubuntu-2404 * fix: build error on docker * Add handle on enter button; --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> Co-authored-by: Gaylord Julien <g.j@mailbox.org> Co-authored-by: Jefferson Nunn <89030989+jeffersonwarrior@users.noreply.github.com> Co-authored-by: Termix CI <ci@termix.dev> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: LukeGus <bugattiguy527@gmail.com>
This commit was merged in pull request #481.
This commit is contained in:
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -17,7 +17,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
@@ -84,7 +84,7 @@ Supported Devices:
|
||||
- MSI Installer
|
||||
- Chocolatey Package Manager
|
||||
- Linux (x64/ia32)
|
||||
- Portable
|
||||
- Portable [(AUR available)](https://aur.archlinux.org/packages/termix-bin)
|
||||
- AppImage
|
||||
- Deb
|
||||
- Flatpak
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface ConfirmationOptions {
|
||||
@@ -9,10 +9,47 @@ interface ConfirmationOptions {
|
||||
variant?: "default" | "destructive";
|
||||
}
|
||||
|
||||
interface ToastConfirmOptions {
|
||||
confirmOnEnter?: boolean;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export function useConfirmation() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [options, setOptions] = useState<ConfirmationOptions | null>(null);
|
||||
const [onConfirm, setOnConfirm] = useState<(() => void) | null>(null);
|
||||
const [activeToastId, setActiveToastId] = useState<string | number | null>(null);
|
||||
const [pendingConfirmCallback, setPendingConfirmCallback] = useState<(() => void) | null>(null);
|
||||
const [pendingResolve, setPendingResolve] = useState<((value: boolean) => void) | null>(null);
|
||||
|
||||
const handleEnterKey = useCallback((event: KeyboardEvent) => {
|
||||
if (event.key === "Enter" && activeToastId !== null) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (pendingConfirmCallback) {
|
||||
pendingConfirmCallback();
|
||||
}
|
||||
if (pendingResolve) {
|
||||
pendingResolve(true);
|
||||
}
|
||||
|
||||
toast.dismiss(activeToastId);
|
||||
setActiveToastId(null);
|
||||
setPendingConfirmCallback(null);
|
||||
setPendingResolve(null);
|
||||
}
|
||||
}, [activeToastId, pendingConfirmCallback, pendingResolve]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeToastId !== null) {
|
||||
// Use capture phase to intercept Enter before terminal receives it
|
||||
window.addEventListener("keydown", handleEnterKey, true);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleEnterKey, true);
|
||||
};
|
||||
}
|
||||
}, [activeToastId, handleEnterKey]);
|
||||
|
||||
const confirm = (opts: ConfirmationOptions, callback: () => void) => {
|
||||
setOptions(opts);
|
||||
@@ -40,6 +77,7 @@ export function useConfirmation() {
|
||||
callback?: () => void,
|
||||
variantOrConfirmLabel: "default" | "destructive" | string = "Confirm",
|
||||
cancelLabel: string = "Cancel",
|
||||
toastOptions: ToastConfirmOptions = { confirmOnEnter: false },
|
||||
): Promise<boolean> => {
|
||||
return new Promise((resolve) => {
|
||||
const isVariant =
|
||||
@@ -47,43 +85,56 @@ export function useConfirmation() {
|
||||
variantOrConfirmLabel === "destructive";
|
||||
const confirmLabel = isVariant ? "Confirm" : variantOrConfirmLabel;
|
||||
|
||||
if (typeof opts === "string") {
|
||||
toast(opts, {
|
||||
action: {
|
||||
label: confirmLabel,
|
||||
onClick: () => {
|
||||
if (callback) callback();
|
||||
resolve(true);
|
||||
},
|
||||
},
|
||||
cancel: {
|
||||
label: cancelLabel,
|
||||
onClick: () => {
|
||||
resolve(false);
|
||||
},
|
||||
},
|
||||
} as any);
|
||||
} else if (typeof opts === "object") {
|
||||
const actualConfirmLabel = opts.confirmText || confirmLabel;
|
||||
const actualCancelLabel = opts.cancelText || cancelLabel;
|
||||
const { confirmOnEnter = false, duration = 8000 } = toastOptions;
|
||||
|
||||
toast(opts.description, {
|
||||
action: {
|
||||
label: actualConfirmLabel,
|
||||
onClick: () => {
|
||||
if (callback) callback();
|
||||
resolve(true);
|
||||
},
|
||||
},
|
||||
cancel: {
|
||||
label: actualCancelLabel,
|
||||
onClick: () => {
|
||||
resolve(false);
|
||||
},
|
||||
},
|
||||
} as any);
|
||||
} else {
|
||||
const handleToastConfirm = () => {
|
||||
if (callback) callback();
|
||||
resolve(true);
|
||||
setActiveToastId(null);
|
||||
setPendingConfirmCallback(null);
|
||||
setPendingResolve(null);
|
||||
};
|
||||
|
||||
const handleToastCancel = () => {
|
||||
resolve(false);
|
||||
setActiveToastId(null);
|
||||
setPendingConfirmCallback(null);
|
||||
setPendingResolve(null);
|
||||
};
|
||||
|
||||
const message = typeof opts === "string" ? opts : opts.description;
|
||||
const actualConfirmLabel = typeof opts === "object" && opts.confirmText ? opts.confirmText : confirmLabel;
|
||||
const actualCancelLabel = typeof opts === "object" && opts.cancelText ? opts.cancelText : cancelLabel;
|
||||
|
||||
const toastId = toast(message, {
|
||||
duration,
|
||||
action: {
|
||||
label: confirmOnEnter ? `${actualConfirmLabel} ↵` : actualConfirmLabel,
|
||||
onClick: handleToastConfirm,
|
||||
},
|
||||
cancel: {
|
||||
label: actualCancelLabel,
|
||||
onClick: handleToastCancel,
|
||||
},
|
||||
onDismiss: () => {
|
||||
setActiveToastId(null);
|
||||
setPendingConfirmCallback(null);
|
||||
setPendingResolve(null);
|
||||
},
|
||||
onAutoClose: () => {
|
||||
resolve(false);
|
||||
setActiveToastId(null);
|
||||
setPendingConfirmCallback(null);
|
||||
setPendingResolve(null);
|
||||
},
|
||||
} as any);
|
||||
|
||||
if (confirmOnEnter) {
|
||||
setActiveToastId(toastId);
|
||||
setPendingConfirmCallback(() => () => {
|
||||
if (callback) callback();
|
||||
});
|
||||
setPendingResolve(() => resolve);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -618,15 +618,15 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
? `${window.location.protocol === "https:" ? "wss" : "ws"}://localhost:30002`
|
||||
: isElectron()
|
||||
? (() => {
|
||||
const baseUrl =
|
||||
(window as { configuredServerUrl?: string })
|
||||
.configuredServerUrl || "http://127.0.0.1:30001";
|
||||
const wsProtocol = baseUrl.startsWith("https://")
|
||||
? "wss://"
|
||||
: "ws://";
|
||||
const wsHost = baseUrl.replace(/^https?:\/\//, "");
|
||||
return `${wsProtocol}${wsHost}/ssh/websocket/`;
|
||||
})()
|
||||
const baseUrl =
|
||||
(window as { configuredServerUrl?: string })
|
||||
.configuredServerUrl || "http://127.0.0.1:30001";
|
||||
const wsProtocol = baseUrl.startsWith("https://")
|
||||
? "wss://"
|
||||
: "ws://";
|
||||
const wsHost = baseUrl.replace(/^https?:\/\//, "");
|
||||
return `${wsProtocol}${wsHost}/ssh/websocket/`;
|
||||
})()
|
||||
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/ssh/websocket/`;
|
||||
|
||||
if (
|
||||
@@ -715,8 +715,11 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
: msg.data;
|
||||
|
||||
terminal.write(outputData);
|
||||
// Universal regex pattern for sudo password prompt
|
||||
// Matches "[sudo]" prefix + any text + ":" suffix, works for all languages
|
||||
// Also matches "sudo: a password is required" for some systems
|
||||
const sudoPasswordPattern =
|
||||
/(?:\[sudo\] password for \S+:|sudo: a password is required)/;
|
||||
/(?:\[sudo\][^\n]*:\s*$|sudo:[^\n]*password[^\n]*required)/i;
|
||||
const passwordToFill =
|
||||
hostConfig.terminalConfig?.sudoPassword || hostConfig.password;
|
||||
if (
|
||||
@@ -746,6 +749,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
},
|
||||
t("common.confirm"),
|
||||
t("common.cancel"),
|
||||
{ confirmOnEnter: true },
|
||||
);
|
||||
setTimeout(() => {
|
||||
sudoPromptShownRef.current = false;
|
||||
@@ -1383,7 +1387,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
|
||||
const selectedCommand =
|
||||
autocompleteSuggestionsRef.current[
|
||||
autocompleteSelectedIndexRef.current
|
||||
autocompleteSelectedIndexRef.current
|
||||
];
|
||||
const currentCmd = currentAutocompleteCommand.current;
|
||||
const completion = selectedCommand.substring(currentCmd.length);
|
||||
|
||||
Reference in New Issue
Block a user