feat(ui): redesign workspace shell with context panel, tabbed sidebars, and faster diff UX (#433)
* feat: tabbed right sidebar, context panel, floating diff comments * fix: auto-close left sidebar when context panel opens - Increase default context panel width from 520 to 600 pixels - Increase sidebar minimum width from 200 to 300 pixels - Replace collapsible component with custom button in diff view * refactoring: rework sidebars, tabs, and file tree layout - Rewrite AnimatedTabs as segment-style with sliding indicator - Upgrade SidebarFilesTree to match FilesView features (context menus, git status, file icons, CRUD dialogs, fuzzy search ranking) - Restructure FilesView header: tabs row + actions row, remove breadcrumbs - Show relative path in context panel header, track active tab - Allow left sidebar to stay open alongside context panel - Hide diff/files tabs from header on desktop (mobile-only) - Move chevron after group name in session sidebar - Compact tab heights in right sidebar and git view - Size PreviewToggleButton to match other action buttons - Remove directory loading spinner from folder icons * feat: add project icon and color customization - Enable users to assign custom icons to projects - Allow users to choose accent colors for projects - Stabilize repo status UI during project switching * feat: add scroll fade indicators to editor tabs * style: reduce spacing and icon sizes in header * style: adjust tab component padding from uniform to vertical-horizontal * feat: Add session state indicators to project tabs * feat: Enhance session status handling and improve UI responsiveness * fix: preserve upstream tracking on branch rename * fix: improve initial remote selection for pull requests - Uses saved remote name from previous session when available - Selects remote based on tracking branch when possible - Falls back to origin or first available remote * perf(diff): faster highlight, stable stacked scroll - split/unified Pierre worker pools; prefer shiki-wasm - align diff CSS line-height; disable scroll anchoring; drop WebKit compositing hacks - harden stacked pin/align (cancel on user scroll/input); prevent overscroll - make overlay scrollbar MutationObserver optional; disable for diff container * feat: handle binary files in diff view * fix: adjust project tabs layout and drag regions * style: update drag overlay visual styling * feat: enable number keys to switch projects in the sidebar * fix: recognize octet-stream as text-based MIME type * feat: add keyboard navigation to context panel * feat: add session pinning to sidebar - Pin important sessions to keep them at the top - Pinned sessions persist across browser sessions * refactor: move context usage display from chat input to header
This commit is contained in:
committed by
GitHub
parent
12606b9e53
commit
47c943b487
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { CodeMirrorEditor, type BlockWidgetDef } from '@/components/ui/CodeMirrorEditor';
|
||||
import { InlineCommentCard, InlineCommentInput } from '@/components/comments';
|
||||
import { CodeMirrorEditor } from '@/components/ui/CodeMirrorEditor';
|
||||
import { useFloatingComments } from '@/components/comments/useFloatingComments';
|
||||
import { PreviewToggleButton } from './PreviewToggleButton';
|
||||
import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer';
|
||||
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
|
||||
@@ -130,6 +130,8 @@ export const PlanView: React.FC = () => {
|
||||
const [lineSelection, setLineSelection] = React.useState<SelectedLineRange | null>(null);
|
||||
const [commentText, setCommentText] = React.useState('');
|
||||
const [editingDraftId, setEditingDraftId] = React.useState<string | null>(null);
|
||||
const editorViewRef = React.useRef<EditorView | null>(null);
|
||||
const editorWrapperRef = React.useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const MD_VIEWER_MODE_KEY = 'openchamber:plan:md-viewer-mode';
|
||||
|
||||
@@ -377,88 +379,33 @@ export const PlanView: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const blockWidgets = React.useMemo(() => {
|
||||
if (mdViewMode === 'preview') return [];
|
||||
const planFileLabel = displayPath ? displayPath.split('/').pop() || 'plan' : 'plan';
|
||||
|
||||
const planFileDrafts = React.useMemo(() => {
|
||||
const sessionKey = getSessionKey();
|
||||
if (!sessionKey) return [];
|
||||
|
||||
const sessionDrafts = allDrafts[sessionKey] ?? [];
|
||||
const fileLabel = displayPath ? displayPath.split('/').pop() || 'plan' : 'plan';
|
||||
const fileDrafts = sessionDrafts.filter((d) => d.source === 'plan' && d.fileLabel === fileLabel);
|
||||
return sessionDrafts.filter((d) => d.source === 'plan' && d.fileLabel === planFileLabel);
|
||||
}, [getSessionKey, allDrafts, planFileLabel]);
|
||||
|
||||
const widgets: BlockWidgetDef[] = [];
|
||||
|
||||
// Add saved drafts
|
||||
fileDrafts.forEach((draft) => {
|
||||
if (draft.id === editingDraftId) {
|
||||
// Always show edit input (even on mobile)
|
||||
widgets.push({
|
||||
afterLine: draft.endLine,
|
||||
id: `edit-${draft.id}`,
|
||||
content: (
|
||||
<InlineCommentInput
|
||||
fileLabel={fileLabel}
|
||||
lineRange={{ start: draft.startLine, end: draft.endLine }}
|
||||
initialText={commentText}
|
||||
onSave={handleSaveComment}
|
||||
onCancel={handleCancelComment}
|
||||
isEditing={true}
|
||||
/>
|
||||
),
|
||||
});
|
||||
} else {
|
||||
// Show saved cards on all devices
|
||||
widgets.push({
|
||||
afterLine: draft.endLine,
|
||||
id: `draft-${draft.id}`,
|
||||
content: (
|
||||
<InlineCommentCard
|
||||
draft={draft}
|
||||
onEdit={() => {
|
||||
setLineSelection({ start: draft.startLine, end: draft.endLine });
|
||||
setCommentText(draft.text);
|
||||
setEditingDraftId(draft.id);
|
||||
}}
|
||||
onDelete={() => removeDraft(draft.sessionKey, draft.id)}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add new comment input if selecting AND not editing an existing draft
|
||||
if (lineSelection && !editingDraftId && !isDragging) {
|
||||
widgets.push({
|
||||
afterLine: lineSelection.end,
|
||||
id: 'plan-new-comment-input',
|
||||
content: (
|
||||
<InlineCommentInput
|
||||
fileLabel={fileLabel}
|
||||
lineRange={lineSelection}
|
||||
initialText={commentText} // Usually empty for new, unless restored?
|
||||
onSave={handleSaveComment}
|
||||
onCancel={handleCancelComment}
|
||||
isEditing={false}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}, [
|
||||
mdViewMode,
|
||||
getSessionKey,
|
||||
allDrafts,
|
||||
displayPath,
|
||||
const floatingComments = useFloatingComments({
|
||||
editorView: editorViewRef.current,
|
||||
wrapperRef: editorWrapperRef,
|
||||
fileDrafts: planFileDrafts,
|
||||
editingDraftId,
|
||||
lineSelection,
|
||||
commentText,
|
||||
handleSaveComment,
|
||||
handleCancelComment,
|
||||
removeDraft,
|
||||
lineSelection,
|
||||
isDragging,
|
||||
]);
|
||||
fileLabel: planFileLabel,
|
||||
onSaveComment: handleSaveComment,
|
||||
onCancelComment: handleCancelComment,
|
||||
onEditDraft: (draft) => {
|
||||
setLineSelection({ start: draft.startLine, end: draft.endLine });
|
||||
setCommentText(draft.text);
|
||||
setEditingDraftId(draft.id);
|
||||
},
|
||||
onDeleteDraft: (draft) => removeDraft(draft.sessionKey, draft.id),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full min-h-0 min-w-0 w-full flex-col overflow-hidden bg-background">
|
||||
@@ -564,7 +511,7 @@ export const PlanView: React.FC = () => {
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative h-full">
|
||||
<div className="relative h-full" ref={editorWrapperRef}>
|
||||
<CodeMirrorEditor
|
||||
value={content}
|
||||
onChange={() => {
|
||||
@@ -573,13 +520,14 @@ export const PlanView: React.FC = () => {
|
||||
readOnly={true}
|
||||
className="h-full [&_.cm-scroller]:pb-[var(--oc-plan-comment-pad)] [&_.cm-scroller]:relative"
|
||||
extensions={editorExtensions}
|
||||
onViewReady={(view) => { editorViewRef.current = view; }}
|
||||
onViewDestroy={() => { editorViewRef.current = null; }}
|
||||
highlightLines={lineSelection
|
||||
? {
|
||||
start: Math.min(lineSelection.start, lineSelection.end),
|
||||
end: Math.max(lineSelection.start, lineSelection.end),
|
||||
}
|
||||
: undefined}
|
||||
blockWidgets={blockWidgets}
|
||||
lineNumbersConfig={{
|
||||
domEventHandlers: {
|
||||
mousedown: (view, line, event) => {
|
||||
@@ -633,6 +581,7 @@ export const PlanView: React.FC = () => {
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{floatingComments}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user