fix(client): refine workspace view controls
This commit is contained in:
@@ -118,11 +118,11 @@ function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }: {
|
||||
// be cut off at the header's bounds.
|
||||
portal
|
||||
anchor={(
|
||||
<Tooltip label={t('groupBy.label')} side="bottom" delayMs={500}>
|
||||
<Tooltip label={t('viewOptions.label')} side="bottom" delayMs={500}>
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(css.iconButton, css.wide)}
|
||||
aria-label={t('groupBy.label')}
|
||||
aria-label={t('viewOptions.label')}
|
||||
onClick={() => { setOpen(v => !v) }}
|
||||
>
|
||||
<IconPersonalizationOutline16 />
|
||||
@@ -163,13 +163,13 @@ type SessionTreeProps = Pick<
|
||||
workspaceExpansion: Readonly<Record<string, boolean>>
|
||||
/** Persist one Workspace group's zero-or-five-session state. */
|
||||
setWorkspaceExpanded: (key: string, expanded: boolean) => void
|
||||
/** Editable orders used by recent-update mode. */
|
||||
/** Shared editable orders used by both Session order modes. */
|
||||
recentSessionOrder: Readonly<Record<string, readonly string[]>>
|
||||
/** Last update timestamps observed by recent-update mode. */
|
||||
/** Last update timestamps observed for one-time recent-update promotions. */
|
||||
recentSessionUpdatedAt: Readonly<Record<string, Readonly<Record<string, number>>>>
|
||||
/** Replace one recent-mode order and its observed timestamps. */
|
||||
/** Replace one shared order and its observed timestamps. */
|
||||
syncRecentSessions: (workspaceKey: string, order: string[], updatedAt: Record<string, number>) => void
|
||||
/** Apply a manual drag inside one recent-mode order. */
|
||||
/** Apply a drag to one shared order. */
|
||||
setRecentSessionOrder: (workspaceKey: string, order: string[]) => void
|
||||
/** Registry-global archive set (hidden rows). */
|
||||
archivedSessionIds: readonly SessionNode['id'][]
|
||||
@@ -181,7 +181,7 @@ type SessionTreeProps = Pick<
|
||||
onSessionRename: (sessionId: SessionNode['id'], currentTitle: string) => void
|
||||
/** Archive a session (row menu action; the row disappears on the state echo). */
|
||||
onSessionArchive: (sessionId: SessionNode['id']) => void
|
||||
/** Session visual order; manual mode drags durable order, updated mode drags its view order. */
|
||||
/** Session order behavior: fixed after edits, or additionally promoted by user activity. */
|
||||
orderBy: SessionOrderBy
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@ function SessionTree({
|
||||
const sessionDropCommitted = useRef(false)
|
||||
const [workspaceDrag, setWorkspaceDrag] = useState<WorkspaceDragState | null>(null)
|
||||
const workspaceDropCommitted = useRef(false)
|
||||
const previousOrderBy = useRef(orderBy)
|
||||
const nativeDragActive = drag !== null || workspaceDrag !== null
|
||||
useEffect(() => {
|
||||
if (!nativeDragActive) return
|
||||
@@ -232,16 +233,18 @@ function SessionTree({
|
||||
[workspaceExpansion],
|
||||
)
|
||||
useEffect(() => {
|
||||
if (orderBy !== 'updated' || list.phase !== 'ready') return
|
||||
if (list.phase !== 'ready') return
|
||||
const switchedToUpdated = previousOrderBy.current !== 'updated' && orderBy === 'updated'
|
||||
previousOrderBy.current = orderBy
|
||||
for (const workspace of workspaces) {
|
||||
const key = workspace.workspaceId as string
|
||||
const sessionIds = workspace.sessionIds.filter(id => list.byId[id] !== undefined)
|
||||
const previousOrder = recentSessionOrder[key]
|
||||
const previousUpdatedAt = recentSessionUpdatedAt[key] ?? {}
|
||||
let nextOrder = reconciledSessionOrder(sessionIds, previousOrder)
|
||||
if (previousOrder === undefined) {
|
||||
if (orderBy === 'updated' && (previousOrder === undefined || switchedToUpdated)) {
|
||||
nextOrder.sort((a, b) => compareSessionRecency(a, b, list.byId))
|
||||
} else {
|
||||
} else if (orderBy === 'updated') {
|
||||
const promoted = sessionIds
|
||||
.filter((id) => {
|
||||
const session = list.byId[id]
|
||||
@@ -270,14 +273,12 @@ function SessionTree({
|
||||
}
|
||||
}, [list, orderBy, recentSessionOrder, recentSessionUpdatedAt, syncRecentSessions, workspaces])
|
||||
const orderedWorkspaces = useMemo(() => {
|
||||
if (orderBy !== 'updated') return workspaces
|
||||
return workspaces.map((workspace) => {
|
||||
const stored = recentSessionOrder[workspace.workspaceId as string]
|
||||
const sessionIds = reconciledSessionOrder(workspace.sessionIds, stored)
|
||||
if (stored === undefined) sessionIds.sort((a, b) => compareSessionRecency(a, b, list.byId))
|
||||
return { ...workspace, sessionIds }
|
||||
})
|
||||
}, [list.byId, orderBy, recentSessionOrder, workspaces])
|
||||
}, [recentSessionOrder, workspaces])
|
||||
const groups = useMemo(
|
||||
() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, { expandedProjects }, 'manual'),
|
||||
[list, orderedWorkspaces, archivedSessionIds, expandedProjects],
|
||||
@@ -846,7 +847,7 @@ export function WorkspaceBrowser({
|
||||
searchInput.current?.focus()
|
||||
}}
|
||||
>
|
||||
<Tooltip label={t('search')} disabled={searchExpanded}>
|
||||
<Tooltip label={t('search')} side="bottom" delayMs={500} disabled={searchExpanded}>
|
||||
<button
|
||||
type="button"
|
||||
className={css.searchButton}
|
||||
|
||||
@@ -10,6 +10,7 @@ export const zh = {
|
||||
'session.new': '新会话',
|
||||
'section.workspaces': '工作区',
|
||||
'section.sessions': '会话',
|
||||
'viewOptions.label': '视图选项',
|
||||
'groupBy.label': '分组方式',
|
||||
'groupBy.workspace': '按工作区',
|
||||
'groupBy.flat': '单列表',
|
||||
@@ -22,7 +23,7 @@ export const zh = {
|
||||
'empty.noMatches': '无匹配结果',
|
||||
'workspace.add': '添加工作区',
|
||||
'search.sessions.aria': '搜索会话',
|
||||
'search.placeholder': '搜索名称、关键词…',
|
||||
'search.placeholder': '搜索会话…',
|
||||
'search.clear': '清除搜索',
|
||||
'search.results.aria': '搜索结果',
|
||||
'search.pending': '正在搜索会话历史…',
|
||||
@@ -78,6 +79,7 @@ export const en = {
|
||||
'session.new': 'New Session',
|
||||
'section.workspaces': 'Workspaces',
|
||||
'section.sessions': 'Sessions',
|
||||
'viewOptions.label': 'View options',
|
||||
'groupBy.label': 'Group by',
|
||||
'groupBy.workspace': 'WorkSpace',
|
||||
'groupBy.flat': 'In one list',
|
||||
@@ -90,7 +92,7 @@ export const en = {
|
||||
'empty.noMatches': 'No matches',
|
||||
'workspace.add': 'Add workspace',
|
||||
'search.sessions.aria': 'Search sessions',
|
||||
'search.placeholder': 'Search name, keywords...',
|
||||
'search.placeholder': 'Search sessions...',
|
||||
'search.clear': 'Clear search',
|
||||
'search.results.aria': 'Search results',
|
||||
'search.pending': 'Searching session history…',
|
||||
|
||||
@@ -9,7 +9,7 @@ import { defineStore, type EngineStoreHandle } from '@deepseek-ai/dsh-client-run
|
||||
|
||||
/** Session-list grouping mode: workspace sections or one flat recency list. */
|
||||
export type WorkspaceGroupBy = 'workspace' | 'flat'
|
||||
/** Session order: durable Workspace order or an activity-promoted editable order. */
|
||||
/** Session order: user-arranged only, or user-arranged plus activity promotion. */
|
||||
export type WorkspaceOrderBy = 'manual' | 'updated'
|
||||
|
||||
/** Workspace browser viewing state persisted across surface remounts and reloads. */
|
||||
@@ -18,9 +18,9 @@ type WorkspaceViewState = {
|
||||
orderBy: WorkspaceOrderBy
|
||||
/** Explicit zero-or-five-session state keyed by Workspace group identity. */
|
||||
workspaceExpansion: Record<string, boolean>
|
||||
/** Editable per-Workspace order used by recent-update mode. */
|
||||
/** Shared editable per-Workspace order; recent-update mode may promote rows within it. */
|
||||
recentSessionOrder: Record<string, string[]>
|
||||
/** Last observed update timestamps used to detect promotion events. */
|
||||
/** Last observed update timestamps used to detect one-time promotion events. */
|
||||
recentSessionUpdatedAt: Record<string, Record<string, number>>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user