Files
paseo/packages/app/src/workspace-pins/launch.tsx
T
Mohamed Boudra 713a042dc5 Add pinnable new-tab targets to the tab row and sidebar
Any new-tab target (new agent, terminal, browser, or a terminal profile)
can be pinned as a quick-launch button. Pins are global client app state
(persisted), not daemon config, so the same set shows on every workspace;
profile pins resolve per host and are dropped where that profile is absent.

The sidebar mirror launches via a navigate-then-launch intent store rather
than calling the create handlers directly: terminal creation only works in
the mounted/focused workspace, so the sidebar requests the launch and the
workspace screen consumes it on focus.
2026-06-15 14:09:29 +07:00

108 lines
3.4 KiB
TypeScript

import { useMemo, type ReactElement } from "react";
import { useTranslation } from "react-i18next";
import { Globe, SquarePen, SquareTerminal } from "lucide-react-native";
import { withUnistyles } from "react-native-unistyles";
import {
getTerminalProfileIcon,
resolveTerminalProfiles,
} from "@getpaseo/protocol/terminal-profiles";
import { getProviderIcon } from "@/components/provider-icons";
import { useDaemonConfig } from "@/hooks/use-daemon-config";
import type { Theme } from "@/styles/theme";
import { pinnedTargetKey, type PinnedTabTarget } from "@/workspace-pins/target";
import { usePinnedTargetsStore } from "@/workspace-pins/store";
export interface ResolvedPin {
key: string;
label: string;
icon: ReactElement;
onPress: () => void;
}
interface UsePinnedLaunchersInput {
serverId: string;
onLaunch: (target: PinnedTabTarget) => void;
}
const mutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
const ThemedSquarePen = withUnistyles(SquarePen);
const ThemedSquareTerminal = withUnistyles(SquareTerminal);
const ThemedGlobe = withUnistyles(Globe);
function ProviderPinIcon({
iconKey,
size,
color = "",
}: {
iconKey: string;
size: number;
color?: string;
}) {
const Icon = getProviderIcon(iconKey);
return <Icon size={size} color={color} />;
}
const ThemedProviderPinIcon = withUnistyles(ProviderPinIcon);
export function ProfileIcon({ iconKey }: { iconKey: string | undefined }): ReactElement {
if (!iconKey) {
return <ThemedSquareTerminal size={14} uniProps={mutedColorMapping} />;
}
return <ThemedProviderPinIcon iconKey={iconKey} size={14} uniProps={mutedColorMapping} />;
}
export function usePinnedLaunchers({ serverId, onLaunch }: UsePinnedLaunchersInput): ResolvedPin[] {
const { t } = useTranslation();
const pinned = usePinnedTargetsStore((state) => state.pinned);
const { config } = useDaemonConfig(serverId);
const profiles = useMemo(
() => resolveTerminalProfiles(config?.terminalProfiles),
[config?.terminalProfiles],
);
return useMemo(() => {
const resolved: ResolvedPin[] = [];
for (const target of pinned) {
if (target.kind === "draft") {
resolved.push({
key: pinnedTargetKey(target),
label: t("workspace.tabs.actions.newAgent"),
icon: <ThemedSquarePen size={14} uniProps={mutedColorMapping} />,
onPress: () => onLaunch(target),
});
continue;
}
if (target.kind === "terminal") {
resolved.push({
key: pinnedTargetKey(target),
label: t("workspace.tabs.actions.newTerminal"),
icon: <ThemedSquareTerminal size={14} uniProps={mutedColorMapping} />,
onPress: () => onLaunch(target),
});
continue;
}
if (target.kind === "browser") {
resolved.push({
key: pinnedTargetKey(target),
label: t("workspace.tabs.actions.newBrowser"),
icon: <ThemedGlobe size={14} uniProps={mutedColorMapping} />,
onPress: () => onLaunch(target),
});
continue;
}
const profile = profiles.find((entry) => entry.id === target.profileId);
if (!profile) {
continue;
}
resolved.push({
key: pinnedTargetKey(target),
label: profile.name,
icon: <ProfileIcon iconKey={getTerminalProfileIcon(profile)} />,
onPress: () => onLaunch(target),
});
}
return resolved;
}, [onLaunch, pinned, profiles, t]);
}