diff --git a/mobile/src/i18n/locales/zh.json b/mobile/src/i18n/locales/zh.json index 1788567f012..2d8d6340c3e 100644 --- a/mobile/src/i18n/locales/zh.json +++ b/mobile/src/i18n/locales/zh.json @@ -1485,7 +1485,6 @@ "updateDesktop": "请更新 {{desktop}} 上的 Orca,以使用自动化。" }, "emptyState": { - "connectedDescription": "桌面将显示在这里。所有数据均采用端到端加密", "connectedTitle": "已连接", "description": "与电脑上的 Orca 配对,即可在手机上查看智能体、进入任意终端并推进工作", "openDesktopDescription": "前往“设置” → “移动端”,生成配对二维码", diff --git a/src/renderer/src/components/new-workspace/ProjectCombobox.tsx b/src/renderer/src/components/new-workspace/ProjectCombobox.tsx index b5d4b9e7969..d244a1db0d8 100644 --- a/src/renderer/src/components/new-workspace/ProjectCombobox.tsx +++ b/src/renderer/src/components/new-workspace/ProjectCombobox.tsx @@ -5,7 +5,6 @@ import { isWithinAddProjectMenu } from '@/components/sidebar/project-admission/m import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover' import { cn } from '@/lib/class-names' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import type { NewWorkspaceProjectOption } from '@/lib/workspace/creation/new-workspace-project-options' import { useTranslate } from '@/i18n/use-translate' import { getAmbiguousProjectOptionIds, @@ -13,19 +12,21 @@ import { sectionProjectOptions } from './project-combobox-matching' import { - focusCascadedAddProjectMenuFirstItem, ProjectComboboxAddProjectOption, type ProjectComboboxAddProjectMenu } from './ProjectComboboxAddProjectOption' +import { + handleProjectComboboxKeyDown, + PROJECT_COMBOBOX_ADD_PROJECT_KEY +} from './project-combobox-keydown' import { ProjectOptionMark, ProjectOptionRow } from './ProjectComboboxRow' import { useRecentProjectIds } from './use-recent-project-ids' import { isWithinComboboxRoot, useTypeAheadCombobox } from './use-type-ahead-combobox' +import type { ProjectComboboxProps } from './project-combobox-props' import { COMBOBOX_FIELD_SHELL, COMBOBOX_POPOVER_SURFACE } from './type-ahead-combobox-styles' -import type { OrcaCloudGitHubRepository } from '@/lib/orca-cloud/use-orca-cloud-github-repositories' import { - CLOUD_REPO_MORE_KEY, - CLOUD_REPO_SHOW_MORE_KEY, cloudRepoKey, + cloudRowKeys, commitCloudRow, filterCloudRepos, ProjectComboboxCloudSection, @@ -34,35 +35,7 @@ import { export type { ProjectComboboxAddProjectMenu } -type ProjectComboboxProps = { - options: readonly NewWorkspaceProjectOption[] - value: string | null - onValueChange: (projectId: string) => void - onValueSelected?: (projectId: string) => void - onAddProject?: () => void - addProjectMenu?: ProjectComboboxAddProjectMenu - placeholder?: string - triggerClassName?: string - invalid?: boolean - describedBy?: string - /** D4: GitHub repositories with no clone here, listed while Orca Cloud is the Run on target. */ - cloudRepos?: readonly OrcaCloudGitHubRepository[] | null - /** The picked cloud-only repository (owner/name); it replaces the project in the field. */ - cloudRepoValue?: string | null - onCloudRepoSelected?: (fullName: string) => void - onMoreCloudAccess?: () => void - /** Present while GitHub has more repositories than are listed. */ - onShowMoreCloudRepos?: (() => void) | null - /** Bumped to open the list at its cloud-only section ("Choose a GitHub repository"). */ - openSignal?: number - /** - * Bumped to move focus here quietly (RR-c1: a stopped Create points at the field's error): no - * path tooltip on that focus, since the error is already speaking. Hover and Tab still show it. - */ - focusSignal?: number -} - -const ADD_PROJECT_KEY = 'add-project' +const ADD_PROJECT_KEY = PROJECT_COMBOBOX_ADD_PROJECT_KEY const ROOT_ATTRIBUTE = 'data-project-combobox-root' /** Use one search field and one shared pointer/keyboard cursor; pin Add project so empty/long lists keep it reachable. */ @@ -101,11 +74,7 @@ export default function ProjectCombobox({ query, recentIds ).flatMap((section) => section.items.map((match) => match.option.id)), - ...(cloudRepos - ? filterCloudRepos(cloudRepos, query).map((repo) => cloudRepoKey(repo.fullName)) - : []), - ...(cloudRepos && onShowMoreCloudRepos ? [CLOUD_REPO_SHOW_MORE_KEY] : []), - ...(cloudRepos && onMoreCloudAccess ? [CLOUD_REPO_MORE_KEY] : []), + ...cloudRowKeys(cloudRepos, query, onShowMoreCloudRepos, onMoreCloudAccess), ...(hasAddProjectRow ? [ADD_PROJECT_KEY] : []) ], [cloudRepos, hasAddProjectRow, onMoreCloudAccess, onShowMoreCloudRepos, options, recentIds] @@ -199,53 +168,30 @@ export default function ProjectCombobox({ onValueSelected?.(key) } - const handleKeyDown = (event: React.KeyboardEvent): void => { - if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { - event.preventDefault() - setCascadeOpen(false) - setOpen(true) - moveArm(event.key === 'ArrowDown' ? 1 : -1) - return - } - if ( - (event.key === 'Enter' || event.key === 'ArrowRight') && - open && - armedKey === ADD_PROJECT_KEY && - addProjectMenu - ) { - event.preventDefault() - setCascadeAutoFocus(true) - if (cascadeOpen) { - focusCascadedAddProjectMenuFirstItem() - } else { - setCascadeOpen(true) - } - return - } - if (event.key === 'Enter' && open) { - event.preventDefault() - commit(armedKey) - return - } - // Handle Escape even while closed to clear stranded queries; bubble only when there is no committed display to restore. - if (event.key === 'Escape' && (open || query.length > 0)) { - event.preventDefault() - event.stopPropagation() - if (cascadeOpen) { - setCascadeOpen(false) - return - } - close() - return - } - if (event.key === 'Backspace' && committed) { - event.preventDefault() - setCascadeOpen(false) - setQuery(committedName) - setOpen(true) - } + // Arming a list row closes an open Add project cascade beside it. + const armClosingCascade = (key: string): void => { + setCascadeOpen(false) + arm(key) } + const handleKeyDown = (event: React.KeyboardEvent): void => + handleProjectComboboxKeyDown(event, { + open, + armedKey, + query, + committed, + committedName, + cascadeOpen, + hasAddProjectMenu: Boolean(addProjectMenu), + setOpen, + setQuery, + setCascadeOpen, + setCascadeAutoFocus, + moveArm, + commit, + close + }) + return ( { - if (cascadeOpen) { - setCascadeOpen(false) - } - arm(scored.option.id) - }} + onArm={() => armClosingCascade(scored.option.id)} onCommit={() => commit(scored.option.id)} /> ))} @@ -451,7 +392,7 @@ export default function ProjectCombobox({ value={cloudRepoValue ? cloudRepoKey(cloudRepoValue) : null} armedKey={armedKey} armedId={`${listId}-armed`} - onArm={arm} + onArm={armClosingCascade} onCommit={commit} showMoreAccess={Boolean(onMoreCloudAccess)} showMoreRepos={Boolean(onShowMoreCloudRepos)} diff --git a/src/renderer/src/components/new-workspace/ProjectComboboxCloudSection.tsx b/src/renderer/src/components/new-workspace/ProjectComboboxCloudSection.tsx index fd50e07e3c3..3b1dd117f4a 100644 --- a/src/renderer/src/components/new-workspace/ProjectComboboxCloudSection.tsx +++ b/src/renderer/src/components/new-workspace/ProjectComboboxCloudSection.tsx @@ -60,6 +60,23 @@ export function commitCloudRow( } /** Case-insensitive substring match; the list is short and names are owner/repo. */ +/** The cloud-only rows' keys, in list order, for the shared keyboard cursor. */ +export function cloudRowKeys( + cloudRepos: readonly OrcaCloudGitHubRepository[] | null, + query: string, + onShowMoreCloudRepos: (() => void) | null, + onMoreCloudAccess: (() => void) | undefined +): string[] { + if (!cloudRepos) { + return [] + } + return [ + ...filterCloudRepos(cloudRepos, query).map((repo) => cloudRepoKey(repo.fullName)), + ...(onShowMoreCloudRepos ? [CLOUD_REPO_SHOW_MORE_KEY] : []), + ...(onMoreCloudAccess ? [CLOUD_REPO_MORE_KEY] : []) + ] +} + export function filterCloudRepos( repos: readonly OrcaCloudGitHubRepository[], query: string diff --git a/src/renderer/src/components/new-workspace/project-combobox-keydown.ts b/src/renderer/src/components/new-workspace/project-combobox-keydown.ts new file mode 100644 index 00000000000..7077703a3d2 --- /dev/null +++ b/src/renderer/src/components/new-workspace/project-combobox-keydown.ts @@ -0,0 +1,72 @@ +import type React from 'react' +import { focusCascadedAddProjectMenuFirstItem } from './ProjectComboboxAddProjectOption' + +export const PROJECT_COMBOBOX_ADD_PROJECT_KEY = 'add-project' + +export type ProjectComboboxKeyDownState = { + open: boolean + armedKey: string | null + query: string + committed: boolean + committedName: string + cascadeOpen: boolean + hasAddProjectMenu: boolean + setOpen: (open: boolean) => void + setQuery: (query: string) => void + setCascadeOpen: (open: boolean) => void + setCascadeAutoFocus: (autoFocus: boolean) => void + moveArm: (step: number) => void + commit: (key: string | null) => void + close: () => void +} + +/** The project field's keys: arrows move the shared cursor, Enter/→ on Add project opens its cascade. */ +export function handleProjectComboboxKeyDown( + event: React.KeyboardEvent, + state: ProjectComboboxKeyDownState +): void { + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + event.preventDefault() + state.setCascadeOpen(false) + state.setOpen(true) + state.moveArm(event.key === 'ArrowDown' ? 1 : -1) + return + } + if ( + (event.key === 'Enter' || event.key === 'ArrowRight') && + state.open && + state.armedKey === PROJECT_COMBOBOX_ADD_PROJECT_KEY && + state.hasAddProjectMenu + ) { + event.preventDefault() + state.setCascadeAutoFocus(true) + if (state.cascadeOpen) { + focusCascadedAddProjectMenuFirstItem() + } else { + state.setCascadeOpen(true) + } + return + } + if (event.key === 'Enter' && state.open) { + event.preventDefault() + state.commit(state.armedKey) + return + } + // Handle Escape even while closed to clear stranded queries; bubble only when there is no committed display to restore. + if (event.key === 'Escape' && (state.open || state.query.length > 0)) { + event.preventDefault() + event.stopPropagation() + if (state.cascadeOpen) { + state.setCascadeOpen(false) + return + } + state.close() + return + } + if (event.key === 'Backspace' && state.committed) { + event.preventDefault() + state.setCascadeOpen(false) + state.setQuery(state.committedName) + state.setOpen(true) + } +} diff --git a/src/renderer/src/components/new-workspace/project-combobox-props.ts b/src/renderer/src/components/new-workspace/project-combobox-props.ts new file mode 100644 index 00000000000..f1928a443d9 --- /dev/null +++ b/src/renderer/src/components/new-workspace/project-combobox-props.ts @@ -0,0 +1,31 @@ +import type { NewWorkspaceProjectOption } from '@/lib/workspace/creation/new-workspace-project-options' +import type { OrcaCloudGitHubRepository } from '@/lib/orca-cloud/use-orca-cloud-github-repositories' +import type { ProjectComboboxAddProjectMenu } from './ProjectComboboxAddProjectOption' + +export type ProjectComboboxProps = { + options: readonly NewWorkspaceProjectOption[] + value: string | null + onValueChange: (projectId: string) => void + onValueSelected?: (projectId: string) => void + onAddProject?: () => void + addProjectMenu?: ProjectComboboxAddProjectMenu + placeholder?: string + triggerClassName?: string + invalid?: boolean + describedBy?: string + /** D4: GitHub repositories with no clone here, listed while Orca Cloud is the Run on target. */ + cloudRepos?: readonly OrcaCloudGitHubRepository[] | null + /** The picked cloud-only repository (owner/name); it replaces the project in the field. */ + cloudRepoValue?: string | null + onCloudRepoSelected?: (fullName: string) => void + onMoreCloudAccess?: () => void + /** Present while GitHub has more repositories than are listed. */ + onShowMoreCloudRepos?: (() => void) | null + /** Bumped to open the list at its cloud-only section ("Choose a GitHub repository"). */ + openSignal?: number + /** + * Bumped to move focus here quietly (RR-c1: a stopped Create points at the field's error): no + * path tooltip on that focus, since the error is already speaking. Hover and Tab still show it. + */ + focusSignal?: number +} diff --git a/src/renderer/src/i18n/en-runtime-required.json b/src/renderer/src/i18n/en-runtime-required.json index 7d632cdf073..fbf6dc532bf 100644 --- a/src/renderer/src/i18n/en-runtime-required.json +++ b/src/renderer/src/i18n/en-runtime-required.json @@ -1472,14 +1472,14 @@ "symlink": "Symbolic links cannot be attached.", "unsupported": "Unsupported file type." }, - "orcaCloudSession": { - "sandboxRestartsInMinutes_one": "Sandbox for {{name}} restarts in {{count}} minute", - "sandboxRestartsInMinutes_other": "Sandbox for {{name}} restarts in {{count}} minutes" - }, "emulator": { "androidSdkNotFound": "Android SDK not found. Install Android Studio and set ANDROID_HOME.", "xcodeSimulatorToolsUnavailable": "Xcode Simulator tools are unavailable. Install full Xcode, open it once, then select it with `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer`." }, + "orcaCloudSession": { + "sandboxRestartsInMinutes_one": "Sandbox for {{name}} restarts in {{count}} minute", + "sandboxRestartsInMinutes_other": "Sandbox for {{name}} restarts in {{count}} minutes" + }, "rateLimits": { "errors": { "chatgptAuthRequired": "ChatGPT authentication required to read rate limits", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 033e785657b..1531137d995 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -2317,7 +2317,6 @@ "hostConnectionFailed": "连接失败", "defaultBranchUpdateFailed": "无法更新项目的默认分支,请重试", "setLocation": "设置项目位置", - "setLocationOnHost": "设置项目在 {{host}} 上的位置", "setLocationTooltip": "选择文件夹,或将此项目克隆到 {{host}}", "connect": "连接", "reconnect": "重新连接", @@ -3404,7 +3403,6 @@ "dabd226118": "关闭", "34dd5ee38b": "重试", "532aea14ce": "取消", - "767951265d": "创建工作树时出现问题", "cancelProvisioning": "取消" } } @@ -18046,7 +18044,6 @@ "signout": { "confirm": { "title": "退出 Orca?", - "description": "重新登录前,产物和 Orca Relay 将不可用,本地项目和工作树不会受到影响", "cancel": "取消", "action": "退出登录" } @@ -20470,7 +20467,6 @@ "startInTerminal": "在终端中启动 {{value0}}" }, "newWorkspace": { - "agentOptional": "代理 [可选]", "createDescription": "选择项目和工作区名称,如果想启动代理,请选择一个", "directSetup": { "run": "Run setup", diff --git a/src/shared/rpc-contract/rpc-params-catalog.generated.ts b/src/shared/rpc-contract/rpc-params-catalog.generated.ts index 2d4c147e442..da72a866286 100644 --- a/src/shared/rpc-contract/rpc-params-catalog.generated.ts +++ b/src/shared/rpc-contract/rpc-params-catalog.generated.ts @@ -44,6 +44,7 @@ import { LocalCommandSettleParamsSchema } from '../local-commands/local-command-contract' import { MobileWebBundleChunkParamsSchema } from '../mobile-web-bundle/bundle-rpc-contract' +import { MobileWebBundleLocaleCatalogParamsSchema } from '../mobile-web-bundle/locale-catalog-contract' import { OrcaCloudEnrollDeviceParamsSchema } from '../orca-cloud-device-enrollment-contract' import { OrcadDiskUsageParamsSchema } from '../orcad-disk-usage' import { OrcadMigrationBlobChunkRequestSchema } from '../orcad-migration/orcad-migration-blob-transfer' @@ -1421,6 +1422,7 @@ export const RPC_PARAMS_BY_METHOD = { 'orcad.migration.stageSnapshotChunk': OrcadMigrationSnapshotChunkRequestSchema, 'orcad.terminalCensus': OrcadTerminalCensusParamsSchema, 'orcad.workVerdict': OrcadWorkVerdictParamsSchema, + 'orchestration.approvalAnswer': ApprovalAnswerParams, 'orchestration.ask': AskParams, 'orchestration.callerShow': null, 'orchestration.check': CheckParams, @@ -1701,6 +1703,7 @@ export const RPC_PARAMS_BY_METHOD = { 'updater.download': null, 'updater.getStatus': null, 'updater.install': null, + 'workspaceCleanup.scan': WorkspaceCleanupScanParams, 'workspacePorts.forwardTunnel': WorkspacePortForwardTunnelParams, 'workspacePorts.kill': WorkspacePortKillParams, 'workspacePorts.killHost': WorkspacePortKillHostParams,