mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
feat: finish the remote-desktop plugin (B14)
This commit is contained in:
1 parent
0af2c94983
commit
14eb558b85
215 files changed
+30029
-3921
No files matched your search
@@ -20,7 +20,6 @@ import {
|
||||
import {
|
||||
fromTriState,
|
||||
toTriState,
|
||||
type RemoteDesktopDefaults,
|
||||
type TerminalDefaults,
|
||||
type TriState,
|
||||
} from "@/lib/connection-defaults";
|
||||
@@ -35,34 +34,6 @@ const inputClass =
|
||||
const labelClass =
|
||||
"text-[10px] font-bold uppercase tracking-widest text-muted-foreground";
|
||||
|
||||
const RDP_TOGGLES = [
|
||||
["forceLossless", "hosts.guac.forceLossless", "hosts.guac.forceLosslessDesc"],
|
||||
["enableWallpaper", "hosts.guac.wallpaper", "hosts.guac.wallpaperDesc"],
|
||||
[
|
||||
"enableFontSmoothing",
|
||||
"hosts.guac.fontSmoothing",
|
||||
"hosts.guac.fontSmoothingDesc",
|
||||
],
|
||||
[
|
||||
"enableDesktopComposition",
|
||||
"hosts.guac.desktopComposition",
|
||||
"hosts.guac.desktopCompositionDesc",
|
||||
],
|
||||
["disableAudio", "hosts.guac.disableAudio", "hosts.guac.disableAudioDesc"],
|
||||
[
|
||||
"enablePrinting",
|
||||
"hosts.guac.enablePrinting",
|
||||
"hosts.guac.enablePrintingDesc",
|
||||
],
|
||||
[
|
||||
"enableDrive",
|
||||
"hosts.guac.enableDriveRedirection",
|
||||
"hosts.guac.enableDriveRedirectionDesc",
|
||||
],
|
||||
["disableCopy", "hosts.guac.disableCopy", "hosts.guac.disableCopyDesc"],
|
||||
["disablePaste", "hosts.guac.disablePaste", "hosts.guac.disablePasteDesc"],
|
||||
] as const;
|
||||
|
||||
function Field({
|
||||
label,
|
||||
children,
|
||||
@@ -105,17 +76,14 @@ export function ConnectionDefaultsSettings() {
|
||||
const { t } = useTranslation();
|
||||
const defaults = useConnectionDefaults();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [tab, setTab] = useState<"shell" | "desktop">("shell");
|
||||
const [terminal, setTerminal] = useState<TerminalDefaults>({});
|
||||
const [rdp, setRdp] = useState<RemoteDesktopDefaults>({});
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [savedThemes, setSavedThemes] = useState<SavedCustomTheme[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!defaults.ready) return;
|
||||
setTerminal(defaults.terminal);
|
||||
setRdp(defaults.rdp);
|
||||
}, [defaults.ready, defaults.terminal, defaults.rdp]);
|
||||
}, [defaults.ready, defaults.terminal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -127,17 +95,13 @@ export function ConnectionDefaultsSettings() {
|
||||
const configuredCount = useMemo(() => {
|
||||
const count = (source: Record<string, unknown>) =>
|
||||
Object.values(source).filter((value) => value !== undefined).length;
|
||||
return count(defaults.terminal) + count(defaults.rdp);
|
||||
}, [defaults.terminal, defaults.rdp]);
|
||||
return count(defaults.terminal);
|
||||
}, [defaults.terminal]);
|
||||
|
||||
const updateTerminal = <K extends keyof TerminalDefaults>(
|
||||
key: K,
|
||||
value: TerminalDefaults[K],
|
||||
) => setTerminal((current) => ({ ...current, [key]: value }));
|
||||
const updateRdp = <K extends keyof RemoteDesktopDefaults>(
|
||||
key: K,
|
||||
value: RemoteDesktopDefaults[K],
|
||||
) => setRdp((current) => ({ ...current, [key]: value }));
|
||||
|
||||
const applySavedTheme = (id: string) => {
|
||||
if (!id) return;
|
||||
@@ -153,10 +117,7 @@ export function ConnectionDefaultsSettings() {
|
||||
async function save() {
|
||||
setSaving(true);
|
||||
try {
|
||||
await Promise.all([
|
||||
defaults.saveTerminalDefaults(terminal),
|
||||
defaults.saveRdpDefaults(rdp),
|
||||
]);
|
||||
await defaults.saveTerminalDefaults(terminal);
|
||||
toast.success(t("newUi.sidebar.connectionDefaults.saved"));
|
||||
setOpen(false);
|
||||
} catch {
|
||||
@@ -204,241 +165,155 @@ export function ConnectionDefaultsSettings() {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex border-b border-border">
|
||||
{(
|
||||
[
|
||||
["shell", "newUi.sidebar.connectionDefaults.tabTerminal"],
|
||||
[
|
||||
"desktop",
|
||||
"newUi.sidebar.connectionDefaults.tabRemoteDesktop",
|
||||
],
|
||||
] as const
|
||||
).map(([id, labelKey]) => (
|
||||
<button
|
||||
key={id}
|
||||
onClick={() => setTab(id)}
|
||||
className={`px-3 py-2 text-xs font-medium border-b-2 -mb-px transition-colors ${
|
||||
tab === id
|
||||
? "border-accent-brand text-foreground"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{t(labelKey)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="max-h-[55vh] overflow-y-auto pr-1">
|
||||
{tab === "shell" ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<Field label={t("hosts.fontFamilyLabel")}>
|
||||
<select
|
||||
className={inputClass}
|
||||
value={terminal.fontFamily ?? ""}
|
||||
onChange={(e) =>
|
||||
updateTerminal("fontFamily", e.target.value || undefined)
|
||||
}
|
||||
>
|
||||
<option value="">{inheritLabel}</option>
|
||||
{TERMINAL_FONTS.map((font) => (
|
||||
<option key={font.value} value={font.value}>
|
||||
{font.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label={t("hosts.colorTheme")}>
|
||||
<select
|
||||
className={inputClass}
|
||||
value={terminal.theme ?? ""}
|
||||
onChange={(e) =>
|
||||
updateTerminal("theme", e.target.value || undefined)
|
||||
}
|
||||
>
|
||||
<option value="">{inheritLabel}</option>
|
||||
{Object.entries(TERMINAL_THEMES)
|
||||
.filter(
|
||||
([key]) =>
|
||||
key !== "termixDark" && key !== "termixLight",
|
||||
)
|
||||
.map(([key, theme]) => (
|
||||
<option key={key} value={key}>
|
||||
{theme.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
{savedThemes.length > 0 && (
|
||||
<Field
|
||||
label={t(
|
||||
"newUi.sidebar.connectionDefaults.savedThemeLabel",
|
||||
)}
|
||||
>
|
||||
<select
|
||||
className={inputClass}
|
||||
value=""
|
||||
onChange={(e) => applySavedTheme(e.target.value)}
|
||||
>
|
||||
<option value="">
|
||||
{t(
|
||||
"newUi.sidebar.connectionDefaults.savedThemePlaceholder",
|
||||
)}
|
||||
</option>
|
||||
{savedThemes.map((theme) => (
|
||||
<option key={theme.id} value={theme.id}>
|
||||
{theme.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
<Field label={t("hosts.fontSizeLabel")}>
|
||||
<input
|
||||
className={inputClass}
|
||||
type="number"
|
||||
min={8}
|
||||
max={32}
|
||||
value={terminal.fontSize ?? ""}
|
||||
placeholder={inheritLabel}
|
||||
onChange={(e) =>
|
||||
updateTerminal(
|
||||
"fontSize",
|
||||
e.target.value ? Number(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label={t("hosts.lineHeightLabel")}>
|
||||
<input
|
||||
className={inputClass}
|
||||
type="number"
|
||||
min={0.8}
|
||||
max={2}
|
||||
step={0.05}
|
||||
value={terminal.lineHeight ?? ""}
|
||||
placeholder={inheritLabel}
|
||||
onChange={(e) =>
|
||||
updateTerminal(
|
||||
"lineHeight",
|
||||
e.target.value ? Number(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label={t("hosts.cursorStyleLabel")}>
|
||||
<select
|
||||
className={inputClass}
|
||||
value={terminal.cursorStyle ?? ""}
|
||||
onChange={(e) =>
|
||||
updateTerminal(
|
||||
"cursorStyle",
|
||||
(e.target.value || undefined) as
|
||||
TerminalDefaults["cursorStyle"] | undefined,
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="">{inheritLabel}</option>
|
||||
{CURSOR_STYLES.map((style) => (
|
||||
<option key={style.value} value={style.value}>
|
||||
{style.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label={t("hosts.scrollbackBufferLabel")}>
|
||||
<input
|
||||
className={inputClass}
|
||||
type="number"
|
||||
min={1000}
|
||||
max={100000}
|
||||
step={1000}
|
||||
value={terminal.scrollback ?? ""}
|
||||
placeholder={inheritLabel}
|
||||
onChange={(e) =>
|
||||
updateTerminal(
|
||||
"scrollback",
|
||||
e.target.value ? Number(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<div className="sm:col-span-2">
|
||||
<SettingRow
|
||||
label={t("hosts.cursorBlinking")}
|
||||
description={t("hosts.cursorBlinkingDesc")}
|
||||
>
|
||||
<TriStateSelect
|
||||
value={terminal.cursorBlink}
|
||||
onChange={(value) => updateTerminal("cursorBlink", value)}
|
||||
/>
|
||||
</SettingRow>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<Field label={t("hosts.guac.colorDepth")}>
|
||||
<select
|
||||
className={inputClass}
|
||||
value={rdp.colorDepth ?? ""}
|
||||
onChange={(e) =>
|
||||
updateRdp(
|
||||
"colorDepth",
|
||||
e.target.value ? Number(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="">{inheritLabel}</option>
|
||||
<option value="16">16-bit</option>
|
||||
<option value="24">24-bit</option>
|
||||
<option value="32">32-bit</option>
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label={t("hosts.guac.resizeMethod")}>
|
||||
<select
|
||||
className={inputClass}
|
||||
value={rdp.resizeMethod ?? ""}
|
||||
onChange={(e) =>
|
||||
updateRdp("resizeMethod", e.target.value || undefined)
|
||||
}
|
||||
>
|
||||
<option value="">{inheritLabel}</option>
|
||||
<option value="display-update">
|
||||
{t(
|
||||
"newUi.sidebar.connectionDefaults.resizeDisplayUpdate",
|
||||
)}
|
||||
</option>
|
||||
<option value="reconnect">
|
||||
{t("newUi.sidebar.connectionDefaults.resizeReconnect")}
|
||||
</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{RDP_TOGGLES.map(([key, labelKey, descriptionKey]) => (
|
||||
<SettingRow
|
||||
key={key}
|
||||
label={t(labelKey)}
|
||||
description={t(descriptionKey)}
|
||||
>
|
||||
<TriStateSelect
|
||||
value={rdp[key] as boolean | undefined}
|
||||
onChange={(value) => updateRdp(key, value)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<Field label={t("hosts.fontFamilyLabel")}>
|
||||
<select
|
||||
className={inputClass}
|
||||
value={terminal.fontFamily ?? ""}
|
||||
onChange={(e) =>
|
||||
updateTerminal("fontFamily", e.target.value || undefined)
|
||||
}
|
||||
>
|
||||
<option value="">{inheritLabel}</option>
|
||||
{TERMINAL_FONTS.map((font) => (
|
||||
<option key={font.value} value={font.value}>
|
||||
{font.label}
|
||||
</option>
|
||||
))}
|
||||
</div>
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label={t("hosts.colorTheme")}>
|
||||
<select
|
||||
className={inputClass}
|
||||
value={terminal.theme ?? ""}
|
||||
onChange={(e) =>
|
||||
updateTerminal("theme", e.target.value || undefined)
|
||||
}
|
||||
>
|
||||
<option value="">{inheritLabel}</option>
|
||||
{Object.entries(TERMINAL_THEMES)
|
||||
.filter(
|
||||
([key]) => key !== "termixDark" && key !== "termixLight",
|
||||
)
|
||||
.map(([key, theme]) => (
|
||||
<option key={key} value={key}>
|
||||
{theme.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
{savedThemes.length > 0 && (
|
||||
<Field
|
||||
label={t("newUi.sidebar.connectionDefaults.savedThemeLabel")}
|
||||
>
|
||||
<select
|
||||
className={inputClass}
|
||||
value=""
|
||||
onChange={(e) => applySavedTheme(e.target.value)}
|
||||
>
|
||||
<option value="">
|
||||
{t(
|
||||
"newUi.sidebar.connectionDefaults.savedThemePlaceholder",
|
||||
)}
|
||||
</option>
|
||||
{savedThemes.map((theme) => (
|
||||
<option key={theme.id} value={theme.id}>
|
||||
{theme.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
<Field label={t("hosts.fontSizeLabel")}>
|
||||
<input
|
||||
className={inputClass}
|
||||
type="number"
|
||||
min={8}
|
||||
max={32}
|
||||
value={terminal.fontSize ?? ""}
|
||||
placeholder={inheritLabel}
|
||||
onChange={(e) =>
|
||||
updateTerminal(
|
||||
"fontSize",
|
||||
e.target.value ? Number(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label={t("hosts.lineHeightLabel")}>
|
||||
<input
|
||||
className={inputClass}
|
||||
type="number"
|
||||
min={0.8}
|
||||
max={2}
|
||||
step={0.05}
|
||||
value={terminal.lineHeight ?? ""}
|
||||
placeholder={inheritLabel}
|
||||
onChange={(e) =>
|
||||
updateTerminal(
|
||||
"lineHeight",
|
||||
e.target.value ? Number(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label={t("hosts.cursorStyleLabel")}>
|
||||
<select
|
||||
className={inputClass}
|
||||
value={terminal.cursorStyle ?? ""}
|
||||
onChange={(e) =>
|
||||
updateTerminal(
|
||||
"cursorStyle",
|
||||
(e.target.value || undefined) as
|
||||
TerminalDefaults["cursorStyle"] | undefined,
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="">{inheritLabel}</option>
|
||||
{CURSOR_STYLES.map((style) => (
|
||||
<option key={style.value} value={style.value}>
|
||||
{style.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label={t("hosts.scrollbackBufferLabel")}>
|
||||
<input
|
||||
className={inputClass}
|
||||
type="number"
|
||||
min={1000}
|
||||
max={100000}
|
||||
step={1000}
|
||||
value={terminal.scrollback ?? ""}
|
||||
placeholder={inheritLabel}
|
||||
onChange={(e) =>
|
||||
updateTerminal(
|
||||
"scrollback",
|
||||
e.target.value ? Number(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<div className="sm:col-span-2">
|
||||
<SettingRow
|
||||
label={t("hosts.cursorBlinking")}
|
||||
description={t("hosts.cursorBlinkingDesc")}
|
||||
>
|
||||
<TriStateSelect
|
||||
value={terminal.cursorBlink}
|
||||
onChange={(value) => updateTerminal("cursorBlink", value)}
|
||||
/>
|
||||
</SettingRow>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-2 pt-3 border-t border-border">
|
||||
@@ -447,7 +322,6 @@ export function ConnectionDefaultsSettings() {
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTerminal({});
|
||||
setRdp({});
|
||||
}}
|
||||
>
|
||||
{t("newUi.sidebar.connectionDefaults.clearAll")}
|
||||
|
||||
Reference in new issue
Block a user