* 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
143 lines
4.0 KiB
TypeScript
143 lines
4.0 KiB
TypeScript
import React, { useMemo, useEffect } from 'react';
|
|
import type { SupportedLanguages } from '@pierre/diffs';
|
|
import { WorkerPoolManager } from '@pierre/diffs/worker';
|
|
|
|
import { useOptionalThemeSystem } from './useThemeSystem';
|
|
import { workerFactory } from '@/lib/diff/workerFactory';
|
|
import { ensurePierreThemeRegistered } from '@/lib/shiki/appThemeRegistry';
|
|
import { getDefaultTheme } from '@/lib/theme/themes';
|
|
// NOTE: keep provider lightweight; avoid main-thread diff parsing here.
|
|
|
|
// Preload common languages for faster initial diff rendering
|
|
const PRELOAD_LANGS: SupportedLanguages[] = [
|
|
// Keep small; workers load others on-demand.
|
|
'typescript',
|
|
'javascript',
|
|
'tsx',
|
|
'jsx',
|
|
'json',
|
|
'markdown',
|
|
];
|
|
|
|
interface DiffWorkerProviderProps {
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
type WorkerPoolStyle = 'unified' | 'split';
|
|
|
|
const WORKER_POOL_CONFIG: Record<WorkerPoolStyle, { poolSize: number; totalASTLRUCacheSize: number; lineDiffType: 'none' | 'word-alt' }> = {
|
|
unified: {
|
|
poolSize: 1,
|
|
totalASTLRUCacheSize: 24,
|
|
lineDiffType: 'none',
|
|
},
|
|
split: {
|
|
poolSize: 2,
|
|
totalASTLRUCacheSize: 56,
|
|
lineDiffType: 'word-alt',
|
|
},
|
|
};
|
|
|
|
let unifiedWorkerPool: WorkerPoolManager | undefined;
|
|
let splitWorkerPool: WorkerPoolManager | undefined;
|
|
|
|
const createWorkerPool = (style: WorkerPoolStyle) => {
|
|
const config = WORKER_POOL_CONFIG[style];
|
|
const pool = new WorkerPoolManager(
|
|
{
|
|
workerFactory,
|
|
poolSize: config.poolSize,
|
|
totalASTLRUCacheSize: config.totalASTLRUCacheSize,
|
|
},
|
|
{
|
|
theme: {
|
|
light: 'pierre-light',
|
|
dark: 'pierre-dark',
|
|
},
|
|
langs: PRELOAD_LANGS,
|
|
lineDiffType: config.lineDiffType,
|
|
preferredHighlighter: 'shiki-wasm',
|
|
}
|
|
);
|
|
void pool.initialize();
|
|
return pool;
|
|
};
|
|
|
|
const getWorkerPool = (style: WorkerPoolStyle): WorkerPoolManager | undefined => {
|
|
if (typeof window === 'undefined') {
|
|
return undefined;
|
|
}
|
|
|
|
if (style === 'split') {
|
|
splitWorkerPool ??= createWorkerPool('split');
|
|
return splitWorkerPool;
|
|
}
|
|
|
|
unifiedWorkerPool ??= createWorkerPool('unified');
|
|
return unifiedWorkerPool;
|
|
};
|
|
|
|
const WorkerPoolWarmup: React.FC<{
|
|
children: React.ReactNode;
|
|
renderTheme: { light: string; dark: string };
|
|
}> = ({ children, renderTheme }) => {
|
|
const unifiedPool = useWorkerPool('unified');
|
|
const splitPool = useWorkerPool('split');
|
|
|
|
useEffect(() => {
|
|
if (unifiedPool) {
|
|
void unifiedPool.setRenderOptions({
|
|
theme: renderTheme,
|
|
lineDiffType: WORKER_POOL_CONFIG.unified.lineDiffType,
|
|
});
|
|
}
|
|
if (splitPool) {
|
|
void splitPool.setRenderOptions({
|
|
theme: renderTheme,
|
|
lineDiffType: WORKER_POOL_CONFIG.split.lineDiffType,
|
|
});
|
|
}
|
|
}, [renderTheme, splitPool, unifiedPool]);
|
|
|
|
return <>{children}</>;
|
|
};
|
|
|
|
export const DiffWorkerProvider: React.FC<DiffWorkerProviderProps> = ({ children }) => {
|
|
const themeSystem = useOptionalThemeSystem();
|
|
|
|
const fallbackLight = getDefaultTheme(false);
|
|
const fallbackDark = getDefaultTheme(true);
|
|
|
|
const lightThemeId = themeSystem?.lightThemeId ?? fallbackLight.metadata.id;
|
|
const darkThemeId = themeSystem?.darkThemeId ?? fallbackDark.metadata.id;
|
|
|
|
const lightTheme =
|
|
themeSystem?.availableThemes.find((theme) => theme.metadata.id === lightThemeId) ??
|
|
fallbackLight;
|
|
const darkTheme =
|
|
themeSystem?.availableThemes.find((theme) => theme.metadata.id === darkThemeId) ??
|
|
fallbackDark;
|
|
|
|
ensurePierreThemeRegistered(lightTheme);
|
|
ensurePierreThemeRegistered(darkTheme);
|
|
|
|
const renderTheme = useMemo(
|
|
() => ({
|
|
light: lightTheme.metadata.id,
|
|
dark: darkTheme.metadata.id,
|
|
}),
|
|
[darkTheme.metadata.id, lightTheme.metadata.id],
|
|
);
|
|
|
|
return (
|
|
<WorkerPoolWarmup renderTheme={renderTheme}>
|
|
{children}
|
|
</WorkerPoolWarmup>
|
|
);
|
|
};
|
|
|
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
export const useWorkerPool = (style: WorkerPoolStyle = 'unified'): WorkerPoolManager | undefined => {
|
|
return useMemo(() => getWorkerPool(style), [style]);
|
|
};
|