Make the command center stable and workspace-aware (#2749)
* feat(app): expand command center actions Keep command center navigation stable by revealing only clipped rows without smooth centering. Prioritize a concise default list while making workspace, Git, pane, and creation actions searchable through the existing contribution and action-policy paths. * test(app): search for query-only command action Add project is intentionally absent from the unfiltered command center. Exercise the overlay flow through search before selecting it.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { withUnistyles } from "react-native-unistyles";
|
||||
import {
|
||||
Archive,
|
||||
ArrowDownUp,
|
||||
Download,
|
||||
GitCommitHorizontal,
|
||||
GitMerge,
|
||||
RefreshCcw,
|
||||
Upload,
|
||||
} from "lucide-react-native";
|
||||
import type { Theme } from "@/styles/theme";
|
||||
|
||||
const ThemedGitCommitHorizontal = withUnistyles(GitCommitHorizontal);
|
||||
const ThemedDownload = withUnistyles(Download);
|
||||
const ThemedUpload = withUnistyles(Upload);
|
||||
const ThemedArrowDownUp = withUnistyles(ArrowDownUp);
|
||||
const ThemedGitMerge = withUnistyles(GitMerge);
|
||||
const ThemedRefreshCcw = withUnistyles(RefreshCcw);
|
||||
const ThemedArchive = withUnistyles(Archive);
|
||||
|
||||
const mutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
||||
|
||||
export const GIT_ACTION_ICONS = {
|
||||
commit: <ThemedGitCommitHorizontal size={16} uniProps={mutedColorMapping} />,
|
||||
pull: <ThemedDownload size={16} uniProps={mutedColorMapping} />,
|
||||
push: <ThemedUpload size={16} uniProps={mutedColorMapping} />,
|
||||
pullAndPush: <ThemedArrowDownUp size={16} uniProps={mutedColorMapping} />,
|
||||
merge: <ThemedGitMerge size={16} uniProps={mutedColorMapping} />,
|
||||
mergeFromBase: <ThemedRefreshCcw size={16} uniProps={mutedColorMapping} />,
|
||||
archive: <ThemedArchive size={16} uniProps={mutedColorMapping} />,
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { View, Text, Pressable, type PressableStateCallbackType } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { ChevronDown, Info, MoreVertical } from "lucide-react-native";
|
||||
import { ChevronDown, MoreVertical } from "lucide-react-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -15,8 +15,8 @@ import { Shortcut } from "@/components/ui/shortcut";
|
||||
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
|
||||
import { inlineUnistylesStyle } from "@/styles/unistyles-inline-style";
|
||||
import type { ShortcutKey } from "@/utils/format-shortcut";
|
||||
import { useToast } from "@/contexts/toast-context";
|
||||
import type { GitAction, GitActions } from "@/git/policy";
|
||||
import { useGitActionRunner } from "@/git/use-actions";
|
||||
|
||||
interface GitActionsSplitButtonProps {
|
||||
gitActions: GitActions;
|
||||
@@ -76,7 +76,7 @@ function GitActionMenuItem({
|
||||
export function GitActionsSplitButton({ gitActions, hideLabels }: GitActionsSplitButtonProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const { t } = useTranslation();
|
||||
const toast = useToast();
|
||||
const runGitAction = useGitActionRunner();
|
||||
const archiveShortcutKeys = useShortcutKeys("archive-workspace");
|
||||
|
||||
const getActionDisplayLabel = useCallback((action: GitAction): string => {
|
||||
@@ -85,26 +85,12 @@ export function GitActionsSplitButton({ gitActions, hideLabels }: GitActionsSpli
|
||||
return action.label;
|
||||
}, []);
|
||||
|
||||
const handleActionSelect = useCallback(
|
||||
(action: GitAction) => {
|
||||
if (action.unavailableMessage) {
|
||||
toast.show(action.unavailableMessage, {
|
||||
durationMs: 3200,
|
||||
icon: <Info size={16} color={theme.colors.foreground} />,
|
||||
});
|
||||
return;
|
||||
}
|
||||
action.handler();
|
||||
},
|
||||
[theme.colors.foreground, toast],
|
||||
);
|
||||
|
||||
const handlePrimaryPress = useCallback(() => {
|
||||
if (!gitActions.primary) {
|
||||
return;
|
||||
}
|
||||
handleActionSelect(gitActions.primary);
|
||||
}, [gitActions.primary, handleActionSelect]);
|
||||
runGitAction(gitActions.primary);
|
||||
}, [gitActions.primary, runGitAction]);
|
||||
|
||||
const overflowMenuButtonStyle = useMemo(() => [styles.iconButton, styles.overflowMenuButton], []);
|
||||
|
||||
@@ -172,7 +158,7 @@ export function GitActionsSplitButton({ gitActions, hideLabels }: GitActionsSpli
|
||||
<GitActionMenuItem
|
||||
key={action.id}
|
||||
action={action}
|
||||
onSelect={handleActionSelect}
|
||||
onSelect={runGitAction}
|
||||
archiveShortcutKeys={archiveShortcutKeys}
|
||||
needsSeparator={action.startsGroup}
|
||||
showSeparator={index > 0}
|
||||
@@ -205,7 +191,7 @@ export function GitActionsSplitButton({ gitActions, hideLabels }: GitActionsSpli
|
||||
<GitActionMenuItem
|
||||
key={action.id}
|
||||
action={action}
|
||||
onSelect={handleActionSelect}
|
||||
onSelect={runGitAction}
|
||||
closeOnSelect={false}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -30,22 +30,15 @@ import { BORDER_WIDTH, ICON_SIZE, SPACING, type Theme } from "@/styles/theme";
|
||||
import { useIsCompactFormFactor, WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
|
||||
import {
|
||||
AlignJustify,
|
||||
Archive,
|
||||
ArrowDownUp,
|
||||
ChevronDown,
|
||||
Columns2,
|
||||
Download,
|
||||
FolderTree,
|
||||
GitCommitHorizontal,
|
||||
GitMerge,
|
||||
List,
|
||||
ListChevronsDownUp,
|
||||
ListChevronsUpDown,
|
||||
Maximize2,
|
||||
Pilcrow,
|
||||
RefreshCcw,
|
||||
RotateCw,
|
||||
Upload,
|
||||
WrapText,
|
||||
} from "lucide-react-native";
|
||||
import { type ParsedDiffFile, type DiffLine, type HighlightToken } from "@/git/use-diff-query";
|
||||
@@ -90,6 +83,7 @@ import { lineNumberGutterWidth } from "@/components/code-insets";
|
||||
import { GitActionsSplitButton } from "@/git/actions-split-button";
|
||||
import { BranchSwitcher } from "@/components/branch-switcher";
|
||||
import { useGitActions } from "@/git/use-actions";
|
||||
import { GIT_ACTION_ICONS } from "@/git/action-icons";
|
||||
import { buildForgeSignInCommand, getForgePresentation, type Forge } from "@/git/forge";
|
||||
import { parseGitRemoteLocation } from "@getpaseo/protocol/git-remote";
|
||||
import type { ForgeAuthState } from "@getpaseo/protocol/messages";
|
||||
@@ -1330,13 +1324,6 @@ const ThemedListChevronsUpDown = withUnistyles(ListChevronsUpDown);
|
||||
const ThemedFolderTree = withUnistyles(FolderTree);
|
||||
const ThemedList = withUnistyles(List);
|
||||
const ThemedMaximize2 = withUnistyles(Maximize2);
|
||||
const ThemedGitCommitHorizontal = withUnistyles(GitCommitHorizontal);
|
||||
const ThemedDownload = withUnistyles(Download);
|
||||
const ThemedUpload = withUnistyles(Upload);
|
||||
const ThemedArrowDownUp = withUnistyles(ArrowDownUp);
|
||||
const ThemedGitMerge = withUnistyles(GitMerge);
|
||||
const ThemedRefreshCcw = withUnistyles(RefreshCcw);
|
||||
const ThemedArchive = withUnistyles(Archive);
|
||||
const ThemedChevronDown = withUnistyles(ChevronDown);
|
||||
const DIFF_OPTIONS_WHITESPACE_ICON = (
|
||||
<ThemedPilcrow size={14} uniProps={foregroundMutedIconColorMapping} />
|
||||
@@ -2823,22 +2810,10 @@ export function GitDiffPane({
|
||||
() => computeBaseRefLabel(baseRef, t("workspace.git.diff.base")),
|
||||
[baseRef, t],
|
||||
);
|
||||
const gitActionsIcons = useMemo(
|
||||
() => ({
|
||||
commit: <ThemedGitCommitHorizontal size={16} uniProps={foregroundMutedIconColorMapping} />,
|
||||
pull: <ThemedDownload size={16} uniProps={foregroundMutedIconColorMapping} />,
|
||||
push: <ThemedUpload size={16} uniProps={foregroundMutedIconColorMapping} />,
|
||||
pullAndPush: <ThemedArrowDownUp size={16} uniProps={foregroundMutedIconColorMapping} />,
|
||||
merge: <ThemedGitMerge size={16} uniProps={foregroundMutedIconColorMapping} />,
|
||||
mergeFromBase: <ThemedRefreshCcw size={16} uniProps={foregroundMutedIconColorMapping} />,
|
||||
archive: <ThemedArchive size={16} uniProps={foregroundMutedIconColorMapping} />,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const { gitActions, branchLabel } = useGitActions({
|
||||
serverId,
|
||||
cwd,
|
||||
icons: gitActionsIcons,
|
||||
icons: GIT_ACTION_ICONS,
|
||||
});
|
||||
const committedDiffDescription = useMemo(
|
||||
() => computeCommittedDiffDescription(branchLabel, baseRefLabel),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useState, useCallback, useEffect, useMemo, type ReactElement } from "react";
|
||||
import { Info } from "lucide-react-native";
|
||||
import { withUnistyles } from "react-native-unistyles";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Theme } from "@/styles/theme";
|
||||
@@ -31,6 +33,26 @@ import { resolveWorkspaceMapKeyByIdentity } from "@/utils/workspace-identity";
|
||||
export type { GitActionId, GitAction, GitActions } from "@/git/policy";
|
||||
|
||||
const forgeMutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
||||
const ThemedInfo = withUnistyles(Info, (theme) => ({ color: theme.colors.foreground }));
|
||||
|
||||
export function useGitActionRunner(): (action: GitAction) => void {
|
||||
const toast = useToast();
|
||||
|
||||
return useCallback(
|
||||
(action: GitAction) => {
|
||||
if (action.disabled) return;
|
||||
if (action.unavailableMessage) {
|
||||
toast.show(action.unavailableMessage, {
|
||||
durationMs: 3200,
|
||||
icon: <ThemedInfo size={16} />,
|
||||
});
|
||||
return;
|
||||
}
|
||||
action.handler();
|
||||
},
|
||||
[toast],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The leading icon for every change-request action (create/view/merge) is the
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { withUnistyles } from "react-native-unistyles";
|
||||
import {
|
||||
Archive,
|
||||
ArrowDownUp,
|
||||
Download,
|
||||
GitCommitHorizontal,
|
||||
GitMerge,
|
||||
RefreshCcw,
|
||||
Upload,
|
||||
} from "lucide-react-native";
|
||||
import { GitActionsSplitButton } from "@/git/actions-split-button";
|
||||
import { useGitActions } from "@/git/use-actions";
|
||||
import type { Theme } from "@/styles/theme";
|
||||
import { GIT_ACTION_ICONS } from "@/git/action-icons";
|
||||
|
||||
interface WorkspaceActionsProps {
|
||||
serverId: string;
|
||||
@@ -18,33 +8,11 @@ interface WorkspaceActionsProps {
|
||||
hideLabels?: boolean;
|
||||
}
|
||||
|
||||
const ThemedGitCommitHorizontal = withUnistyles(GitCommitHorizontal);
|
||||
const ThemedDownload = withUnistyles(Download);
|
||||
const ThemedUpload = withUnistyles(Upload);
|
||||
const ThemedArrowDownUp = withUnistyles(ArrowDownUp);
|
||||
const ThemedGitMerge = withUnistyles(GitMerge);
|
||||
const ThemedRefreshCcw = withUnistyles(RefreshCcw);
|
||||
const ThemedArchive = withUnistyles(Archive);
|
||||
|
||||
const mutedColorMapping = (theme: Theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
});
|
||||
|
||||
const ICONS = {
|
||||
commit: <ThemedGitCommitHorizontal size={16} uniProps={mutedColorMapping} />,
|
||||
pull: <ThemedDownload size={16} uniProps={mutedColorMapping} />,
|
||||
push: <ThemedUpload size={16} uniProps={mutedColorMapping} />,
|
||||
pullAndPush: <ThemedArrowDownUp size={16} uniProps={mutedColorMapping} />,
|
||||
merge: <ThemedGitMerge size={16} uniProps={mutedColorMapping} />,
|
||||
mergeFromBase: <ThemedRefreshCcw size={16} uniProps={mutedColorMapping} />,
|
||||
archive: <ThemedArchive size={16} uniProps={mutedColorMapping} />,
|
||||
};
|
||||
|
||||
export function WorkspaceActions({ serverId, cwd, hideLabels }: WorkspaceActionsProps) {
|
||||
const { gitActions } = useGitActions({
|
||||
serverId,
|
||||
cwd,
|
||||
icons: ICONS,
|
||||
icons: GIT_ACTION_ICONS,
|
||||
});
|
||||
|
||||
return <GitActionsSplitButton gitActions={gitActions} hideLabels={hideLabels} />;
|
||||
|
||||
Reference in New Issue
Block a user