feat: finish the remote-desktop plugin (B14)

This commit is contained in:
LukeGus committed 2026-09-24 01:02:45 -05:00
1 parent 0af2c94983
commit 14eb558b85
215 files changed
+30029 -3921

No files matched your search

+150 -276
View File
@@ -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")}