Merge remote-tracking branch 'origin/doc/host-client-group-readmes' into feat/directory-picker
# Conflicts: # packages/client/connection/src/client/fixture.ts # packages/client/connection/tests/fake-api.ts # packages/client/runtime/src/client/workspaces/service.ts # packages/client/runtime/tests/fake-api.ts # packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx # packages/client/ui-workspace/src/client/WorkspacePicker.tsx # packages/client/ui-workspace/tests/workspace-picker.spec.tsx # packages/host/apiproxy/README.i18n.yaml # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/host.schema.ts # packages/host/apiproxy/src/api/host.ts # packages/host/apiproxy/src/api/rpc-map.ts # packages/host/apiproxy/src/fetch/client.ts # packages/host/apiproxy/src/fetch/handler.ts # packages/host/apiproxy/tests/api-proxy-workspace.spec.ts # packages/host/apiproxy/tests/client-handler.spec.ts # packages/host/apiproxy/tests/fetch-carrier.spec.ts
This commit is contained in:
@@ -129,6 +129,7 @@
|
||||
reads the shell's class names): the two icon controls stack as 36x36
|
||||
circles matching the shell's rail rhythm. */
|
||||
.rail .sectionHeader {
|
||||
gap: 0;
|
||||
padding-left: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@@ -207,6 +208,13 @@
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 12px;
|
||||
/* Row trailing content (the relative time, and the hover action buttons
|
||||
that replace it) sits flush against the row's 8px right padding, so an
|
||||
overlaid scrollbar covers it. Reserving the gutter keeps the bar beside
|
||||
the rows instead of on top of them; `stable` holds the reservation when
|
||||
the list is short enough not to scroll, so expanding a group does not
|
||||
shift every row left. */
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
/* One workspace section: header row + expanded session run. Rows inside
|
||||
|
||||
@@ -266,6 +266,7 @@ export function WorkspaceBrowser({
|
||||
// states; the menu anchors on this button).
|
||||
const [wsPickerOpen, setWsPickerOpen] = useState(false)
|
||||
const wsPlusRef = useRef<HTMLButtonElement>(null)
|
||||
const composingRef = useRef(false)
|
||||
|
||||
// Rail search = expand + land in the search box: the flag arms before the
|
||||
// expand request; once the shell flips wide the input mounts and takes focus.
|
||||
@@ -359,7 +360,6 @@ export function WorkspaceBrowser({
|
||||
className={css.iconButton}
|
||||
aria-label="Create workspace"
|
||||
onClick={() => {
|
||||
if (!wide) expandSidebar()
|
||||
setWsPickerOpen(v => !v)
|
||||
}}
|
||||
>
|
||||
@@ -374,6 +374,8 @@ export function WorkspaceBrowser({
|
||||
createWorkspace={createWorkspace}
|
||||
pickDirectory={pickDirectory}
|
||||
directoryPickerKind={directoryPickerKind}
|
||||
createOnly
|
||||
side="right"
|
||||
onPick={(workspaceId) => {
|
||||
setWsPickerOpen(false)
|
||||
startSession(workspaceId)
|
||||
@@ -461,9 +463,12 @@ export function WorkspaceBrowser({
|
||||
aria-label="Workspace name"
|
||||
autoFocus
|
||||
disabled={renaming}
|
||||
onFocus={(e) => { e.target.select() }}
|
||||
onChange={(e) => { setRenameDraft(e.target.value); setRenameError(null) }}
|
||||
onCompositionStart={() => { composingRef.current = true }}
|
||||
onCompositionEnd={() => { composingRef.current = false }}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (e.key === 'Enter' && !composingRef.current) {
|
||||
e.preventDefault()
|
||||
confirmRename()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* slot registration.
|
||||
*/
|
||||
import type { RefObject } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
Button, IconFolderClose16, IconPlusOutline16, Menu, Modal, type MenuEntry,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
@@ -39,6 +39,12 @@ export interface WorkspaceCreateFlowProps {
|
||||
onPick: (workspaceId: WorkspaceId) => void
|
||||
/** Close the popover (outside click / Escape / post-pick). */
|
||||
onClose: () => void
|
||||
/** Only show create actions (open folder / create new), hide existing workspaces. */
|
||||
createOnly?: boolean
|
||||
/** Menu opening direction relative to the anchor. */
|
||||
side?: 'bottom' | 'top' | 'right'
|
||||
/** Currently active workspace (trailing check in the picker list). */
|
||||
selectedId?: WorkspaceId | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,6 +61,9 @@ export function WorkspaceCreateFlow({
|
||||
directoryPickerKind,
|
||||
onPick,
|
||||
onClose,
|
||||
createOnly = false,
|
||||
side = 'bottom',
|
||||
selectedId,
|
||||
}: WorkspaceCreateFlowProps) {
|
||||
const workspaceSnapshot = useWorkspaces(state => state)
|
||||
const workspaces = workspaceSnapshot.items
|
||||
@@ -68,6 +77,7 @@ export function WorkspaceCreateFlow({
|
||||
const [modalError, setModalError] = useState<string | null>(null)
|
||||
const [pickingFolder, setPickingFolder] = useState(false)
|
||||
const [folderConflict, setFolderConflict] = useState(false)
|
||||
const composingRef = useRef(false)
|
||||
const normalizedWorkspaceName = workspaceName.trim()
|
||||
const duplicateWorkspaceName = !creating && normalizedWorkspaceName !== ''
|
||||
&& workspaces.some(workspace => workspace.title === normalizedWorkspaceName)
|
||||
@@ -100,19 +110,23 @@ export function WorkspaceCreateFlow({
|
||||
return () => { stale = true }
|
||||
}, [open, directoryPickerKind])
|
||||
|
||||
const items: MenuEntry[] = [
|
||||
...workspaces.map(workspace => ({
|
||||
id: workspace.workspaceId,
|
||||
label: workspace.title,
|
||||
icon: <IconFolderClose16 size={16} />,
|
||||
disabled: pickingFolder,
|
||||
})),
|
||||
...(workspaces.length > 0 ? [{ type: 'separator' as const, id: 'sep-create' }] : []),
|
||||
const createEntries: MenuEntry[] = [
|
||||
...(nativePicker
|
||||
? [{ id: OPEN_LOCAL_FOLDER, label: 'Open local folder…', icon: <IconFolderClose16 size={16} />, disabled: pickingFolder }]
|
||||
: []),
|
||||
{ id: CREATE_NEW, label: 'Create a new workspace', icon: <IconPlusOutline16 size={16} />, disabled: pickingFolder },
|
||||
]
|
||||
// With workspaces listed, the create actions pin below the scroll region
|
||||
// (divider + always visible); otherwise they ARE the menu.
|
||||
const pinCreate = !createOnly && workspaces.length > 0
|
||||
const items: MenuEntry[] = pinCreate
|
||||
? workspaces.map(workspace => ({
|
||||
id: workspace.workspaceId,
|
||||
label: workspace.title,
|
||||
icon: <IconFolderClose16 size={16} />,
|
||||
disabled: pickingFolder,
|
||||
}))
|
||||
: createEntries
|
||||
|
||||
const closeModal = (): void => {
|
||||
if (creating) return
|
||||
@@ -147,7 +161,7 @@ export function WorkspaceCreateFlow({
|
||||
}
|
||||
if (id === CREATE_NEW) {
|
||||
onClose()
|
||||
setWorkspaceName('workspace')
|
||||
setWorkspaceName('')
|
||||
setModalError(null)
|
||||
setModalKind('create')
|
||||
return
|
||||
@@ -182,8 +196,11 @@ export function WorkspaceCreateFlow({
|
||||
open={open}
|
||||
anchor={null}
|
||||
items={items}
|
||||
{...pinCreate ? { footer: createEntries } : {}}
|
||||
selectedId={selectedId}
|
||||
onSelect={handleSelect}
|
||||
onClose={onClose}
|
||||
side={side}
|
||||
portal
|
||||
getAnchorRect={getAnchorRect}
|
||||
/>
|
||||
@@ -227,12 +244,15 @@ export function WorkspaceCreateFlow({
|
||||
<input
|
||||
className={css.modalInput}
|
||||
value={workspaceName}
|
||||
placeholder="Workspace name"
|
||||
aria-label="New workspace name"
|
||||
autoFocus
|
||||
disabled={creating}
|
||||
onChange={(event) => { setWorkspaceName(event.target.value); setModalError(null) }}
|
||||
onCompositionStart={() => { composingRef.current = true }}
|
||||
onCompositionEnd={() => { composingRef.current = false }}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
if (event.key === 'Enter' && !composingRef.current) {
|
||||
event.preventDefault()
|
||||
confirmCreate()
|
||||
}
|
||||
@@ -258,6 +278,7 @@ export function WorkspacePicker({
|
||||
open,
|
||||
anchorRef,
|
||||
useWorkspaces,
|
||||
selectedId,
|
||||
onPick,
|
||||
onClose,
|
||||
createWorkspace,
|
||||
@@ -272,6 +293,7 @@ export function WorkspacePicker({
|
||||
createWorkspace={createWorkspace}
|
||||
pickDirectory={pickDirectory}
|
||||
directoryPickerKind={directoryPickerKind}
|
||||
selectedId={selectedId}
|
||||
onPick={onPick}
|
||||
onClose={onClose}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user