fix(sidebar): add project sort modes (#2067)
* fix(sidebar): add project sort modes * fix(sidebar): preserve manual sort order * fix(sidebar): move sort state before render --------- Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
co-authored by
bashrusakh
parent
f6326e1c35
commit
bfaf62e222
@@ -921,6 +921,9 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
color?: string;
|
color?: string;
|
||||||
iconImage?: { mime: string; updatedAt: number; source: 'custom' | 'auto' };
|
iconImage?: { mime: string; updatedAt: number; source: 'custom' | 'auto' };
|
||||||
iconBackground?: string;
|
iconBackground?: string;
|
||||||
|
addedAt?: number;
|
||||||
|
lastOpenedAt?: number;
|
||||||
|
sidebarCollapsed?: boolean;
|
||||||
}>;
|
}>;
|
||||||
}, [projects]);
|
}, [projects]);
|
||||||
|
|
||||||
@@ -1019,13 +1022,84 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
lastRepoStatusRef.current = Boolean(projectRepoStatus.get(activeProjectId));
|
lastRepoStatusRef.current = Boolean(projectRepoStatus.get(activeProjectId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showRecentSection = useSessionDisplayStore((state) => state.showRecentSection);
|
||||||
|
const showArchivedSessions = useSessionDisplayStore((state) => state.showArchivedSessions);
|
||||||
|
const projectSortOrder = useSessionDisplayStore((state) => state.projectSortOrder);
|
||||||
|
const manualProjectOrder = useProjectsStore((state) => state.manualProjectOrder);
|
||||||
|
|
||||||
|
const recentProjectIds = React.useMemo(() => {
|
||||||
|
const recentSessions = deriveRecentSessions(sessions);
|
||||||
|
if (recentSessions.length === 0) return new Set<string>();
|
||||||
|
|
||||||
|
const pathToId = new Map<string, string>();
|
||||||
|
for (const project of normalizedProjects) {
|
||||||
|
if (project.normalizedPath) {
|
||||||
|
pathToId.set(project.normalizedPath, project.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = new Set<string>();
|
||||||
|
for (const session of recentSessions) {
|
||||||
|
const directory = normalizePath((session as Session & { directory?: string | null }).directory ?? null);
|
||||||
|
if (directory) {
|
||||||
|
const projectId = pathToId.get(directory);
|
||||||
|
if (projectId) ids.add(projectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}, [sessions, normalizedProjects]);
|
||||||
|
|
||||||
|
const sortedProjects = React.useMemo(() => {
|
||||||
|
const list = [...normalizedProjects];
|
||||||
|
|
||||||
|
switch (projectSortOrder) {
|
||||||
|
case 'a-z':
|
||||||
|
list.sort((a, b) => {
|
||||||
|
const aLabel = (a.label || a.path).toLowerCase();
|
||||||
|
const bLabel = (b.label || b.path).toLowerCase();
|
||||||
|
return aLabel.localeCompare(bLabel);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'z-a':
|
||||||
|
list.sort((a, b) => {
|
||||||
|
const aLabel = (a.label || a.path).toLowerCase();
|
||||||
|
const bLabel = (b.label || b.path).toLowerCase();
|
||||||
|
return bLabel.localeCompare(aLabel);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'date-added':
|
||||||
|
list.sort((a, b) => (b.addedAt ?? 0) - (a.addedAt ?? 0));
|
||||||
|
break;
|
||||||
|
case 'recent':
|
||||||
|
list.sort((a, b) => (b.lastOpenedAt ?? 0) - (a.lastOpenedAt ?? 0));
|
||||||
|
break;
|
||||||
|
case 'manual': {
|
||||||
|
const orderMap = new Map(manualProjectOrder.map((id, i) => [id, i]));
|
||||||
|
list.sort((a, b) => {
|
||||||
|
const ai = orderMap.get(a.id) ?? Infinity;
|
||||||
|
const bi = orderMap.get(b.id) ?? Infinity;
|
||||||
|
return ai - bi;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectSortOrder === 'manual') {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recent = list.filter((p) => recentProjectIds.has(p.id));
|
||||||
|
const rest = list.filter((p) => !recentProjectIds.has(p.id));
|
||||||
|
return [...recent, ...rest];
|
||||||
|
}, [normalizedProjects, projectSortOrder, manualProjectOrder, recentProjectIds]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
projectSections,
|
projectSections,
|
||||||
groupSearchDataByGroup,
|
groupSearchDataByGroup,
|
||||||
sectionsForRender,
|
sectionsForRender,
|
||||||
searchMatchCount,
|
searchMatchCount,
|
||||||
} = useSessionSidebarSections({
|
} = useSessionSidebarSections({
|
||||||
normalizedProjects,
|
normalizedProjects: sortedProjects,
|
||||||
getSessionsForProject,
|
getSessionsForProject,
|
||||||
getArchivedSessionsForProject,
|
getArchivedSessionsForProject,
|
||||||
availableWorktreesByProject,
|
availableWorktreesByProject,
|
||||||
@@ -1131,9 +1205,6 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
return meta;
|
return meta;
|
||||||
}, [projectSections, homeDirectory]);
|
}, [projectSections, homeDirectory]);
|
||||||
|
|
||||||
const showRecentSection = useSessionDisplayStore((state) => state.showRecentSection);
|
|
||||||
const showArchivedSessions = useSessionDisplayStore((state) => state.showArchivedSessions);
|
|
||||||
|
|
||||||
const activeNowSessions = React.useMemo(() => {
|
const activeNowSessions = React.useMemo(() => {
|
||||||
if (!showRecentSection || isVSCode) {
|
if (!showRecentSection || isVSCode) {
|
||||||
return [];
|
return [];
|
||||||
@@ -1636,6 +1707,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
removeProject={removeProject}
|
removeProject={removeProject}
|
||||||
projectHeaderSentinelRefs={projectHeaderSentinelRefs}
|
projectHeaderSentinelRefs={projectHeaderSentinelRefs}
|
||||||
reorderProjects={reorderProjects}
|
reorderProjects={reorderProjects}
|
||||||
|
projectSortOrder={projectSortOrder}
|
||||||
getOrderedGroups={getOrderedGroups}
|
getOrderedGroups={getOrderedGroups}
|
||||||
setGroupOrderByProject={setGroupOrderByProject}
|
setGroupOrderByProject={setGroupOrderByProject}
|
||||||
openSidebarMenuKey={openSidebarMenuKey}
|
openSidebarMenuKey={openSidebarMenuKey}
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
|||||||
const setDisplayMode = useSessionDisplayStore((state) => state.setDisplayMode);
|
const setDisplayMode = useSessionDisplayStore((state) => state.setDisplayMode);
|
||||||
const toggleRecentSection = useSessionDisplayStore((state) => state.toggleRecentSection);
|
const toggleRecentSection = useSessionDisplayStore((state) => state.toggleRecentSection);
|
||||||
const toggleArchivedSessions = useSessionDisplayStore((state) => state.toggleArchivedSessions);
|
const toggleArchivedSessions = useSessionDisplayStore((state) => state.toggleArchivedSessions);
|
||||||
|
const projectSortOrder = useSessionDisplayStore((state) => state.projectSortOrder);
|
||||||
|
const setProjectSortOrder = useSessionDisplayStore((state) => state.setProjectSortOrder);
|
||||||
// VS Code forces the expanded layout, so the mode toggle is meaningless there.
|
// VS Code forces the expanded layout, so the mode toggle is meaningless there.
|
||||||
const showDisplayModeToggle = !isVSCodeRuntime();
|
const showDisplayModeToggle = !isVSCodeRuntime();
|
||||||
|
|
||||||
@@ -175,6 +177,60 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
|||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={headerActionButtonClass}
|
||||||
|
aria-label={t('sessions.sidebar.header.actions.sortProjects')}
|
||||||
|
>
|
||||||
|
<Icon name="arrow-up-double" className={headerActionIconClass} />
|
||||||
|
</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.sortProjects')}</p></TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<DropdownMenuContent align="end" className="min-w-[160px]">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setProjectSortOrder('manual')}
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<span>{t('sessions.sidebar.header.projectSort.manual')}</span>
|
||||||
|
{projectSortOrder === 'manual' ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setProjectSortOrder('a-z')}
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<span>{t('sessions.sidebar.header.projectSort.aToZ')}</span>
|
||||||
|
{projectSortOrder === 'a-z' ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setProjectSortOrder('z-a')}
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<span>{t('sessions.sidebar.header.projectSort.zToA')}</span>
|
||||||
|
{projectSortOrder === 'z-a' ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setProjectSortOrder('date-added')}
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<span>{t('sessions.sidebar.header.projectSort.dateAdded')}</span>
|
||||||
|
{projectSortOrder === 'date-added' ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setProjectSortOrder('recent')}
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<span>{t('sessions.sidebar.header.projectSort.recent')}</span>
|
||||||
|
{projectSortOrder === 'recent' ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@@ -237,6 +293,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
|||||||
<Icon name="expand-up-down" className="h-4 w-4" />
|
<Icon name="expand-up-down" className="h-4 w-4" />
|
||||||
<span>{t('sessions.sidebar.header.displayMode.expandAll')}</span>
|
<span>{t('sessions.sidebar.header.displayMode.expandAll')}</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { SortableGroupItem, SortableProjectItem } from './sortableItems';
|
|||||||
import { formatProjectLabel } from './utils';
|
import { formatProjectLabel } from './utils';
|
||||||
import { useI18n } from '@/lib/i18n';
|
import { useI18n } from '@/lib/i18n';
|
||||||
import type { MainTab } from '@/stores/useUIStore';
|
import type { MainTab } from '@/stores/useUIStore';
|
||||||
|
import type { ProjectSortOrder } from '@/stores/useSessionDisplayStore';
|
||||||
|
|
||||||
type ProjectSection = {
|
type ProjectSection = {
|
||||||
project: {
|
project: {
|
||||||
@@ -69,6 +70,7 @@ type Props = {
|
|||||||
removeProject: (id: string) => void;
|
removeProject: (id: string) => void;
|
||||||
projectHeaderSentinelRefs: React.MutableRefObject<Map<string, HTMLDivElement | null>>;
|
projectHeaderSentinelRefs: React.MutableRefObject<Map<string, HTMLDivElement | null>>;
|
||||||
reorderProjects: (fromIndex: number, toIndex: number) => void;
|
reorderProjects: (fromIndex: number, toIndex: number) => void;
|
||||||
|
projectSortOrder: ProjectSortOrder;
|
||||||
getOrderedGroups: (projectId: string, groups: SessionGroup[]) => SessionGroup[];
|
getOrderedGroups: (projectId: string, groups: SessionGroup[]) => SessionGroup[];
|
||||||
setGroupOrderByProject: React.Dispatch<React.SetStateAction<Map<string, string[]>>>;
|
setGroupOrderByProject: React.Dispatch<React.SetStateAction<Map<string, string[]>>>;
|
||||||
openSidebarMenuKey: string | null;
|
openSidebarMenuKey: string | null;
|
||||||
@@ -188,6 +190,8 @@ export function SidebarProjectsList(props: Props): React.ReactNode {
|
|||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
onDragEnd={(event) => {
|
onDragEnd={(event) => {
|
||||||
if (props.isInlineEditing) return;
|
if (props.isInlineEditing) return;
|
||||||
|
// Drag only allowed in manual sort mode - indices from visual order don't match store order in other modes
|
||||||
|
if (props.projectSortOrder !== 'manual') return;
|
||||||
const { active, over } = event;
|
const { active, over } = event;
|
||||||
if (!over || active.id === over.id) return;
|
if (!over || active.id === over.id) return;
|
||||||
const oldIndex = props.sectionsForRender.findIndex((section) => section.project.id === active.id);
|
const oldIndex = props.sectionsForRender.findIndex((section) => section.project.id === active.id);
|
||||||
@@ -219,6 +223,7 @@ export function SidebarProjectsList(props: Props): React.ReactNode {
|
|||||||
<SortableProjectItem
|
<SortableProjectItem
|
||||||
key={projectKey}
|
key={projectKey}
|
||||||
id={projectKey}
|
id={projectKey}
|
||||||
|
disabled={props.projectSortOrder !== 'manual'}
|
||||||
projectLabel={projectLabel}
|
projectLabel={projectLabel}
|
||||||
projectDescription={projectDescription}
|
projectDescription={projectDescription}
|
||||||
projectIcon={project.icon}
|
projectIcon={project.icon}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { useI18n } from '@/lib/i18n';
|
|||||||
|
|
||||||
export interface SortableProjectItemProps {
|
export interface SortableProjectItemProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
disabled?: boolean;
|
||||||
projectLabel: string;
|
projectLabel: string;
|
||||||
projectDescription: string;
|
projectDescription: string;
|
||||||
projectIcon?: string;
|
projectIcon?: string;
|
||||||
@@ -51,6 +52,7 @@ export type SortableDragHandleProps = {
|
|||||||
|
|
||||||
export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
||||||
id,
|
id,
|
||||||
|
disabled = false,
|
||||||
projectLabel,
|
projectLabel,
|
||||||
projectDescription,
|
projectDescription,
|
||||||
projectIcon,
|
projectIcon,
|
||||||
@@ -85,7 +87,7 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
|||||||
transform,
|
transform,
|
||||||
transition,
|
transition,
|
||||||
isDragging,
|
isDragging,
|
||||||
} = useSortable({ id });
|
} = useSortable({ id, disabled });
|
||||||
|
|
||||||
const suppressNextToggleRef = React.useRef(false);
|
const suppressNextToggleRef = React.useRef(false);
|
||||||
const menuInstanceKey = `project:${id}`;
|
const menuInstanceKey = `project:${id}`;
|
||||||
|
|||||||
@@ -382,6 +382,7 @@ export const dict = {
|
|||||||
'sessions.sidebar.header.actions.searchSessions': 'Search sessions',
|
'sessions.sidebar.header.actions.searchSessions': 'Search sessions',
|
||||||
'sessions.sidebar.header.actions.exitSelection': 'Exit selection',
|
'sessions.sidebar.header.actions.exitSelection': 'Exit selection',
|
||||||
'sessions.sidebar.header.actions.selectSessions': 'Select sessions',
|
'sessions.sidebar.header.actions.selectSessions': 'Select sessions',
|
||||||
|
'sessions.sidebar.header.actions.sortProjects': 'Sort projects',
|
||||||
'sessions.sidebar.header.actions.sessionDisplayMode': 'Session display mode',
|
'sessions.sidebar.header.actions.sessionDisplayMode': 'Session display mode',
|
||||||
'sessions.sidebar.header.displayMode.label': 'Display mode',
|
'sessions.sidebar.header.displayMode.label': 'Display mode',
|
||||||
'sessions.sidebar.header.displayMode.default': 'Default',
|
'sessions.sidebar.header.displayMode.default': 'Default',
|
||||||
@@ -390,6 +391,11 @@ export const dict = {
|
|||||||
'sessions.sidebar.header.displayMode.showArchived': 'Show archived sessions',
|
'sessions.sidebar.header.displayMode.showArchived': 'Show archived sessions',
|
||||||
'sessions.sidebar.header.displayMode.collapseAll': 'Collapse all',
|
'sessions.sidebar.header.displayMode.collapseAll': 'Collapse all',
|
||||||
'sessions.sidebar.header.displayMode.expandAll': 'Expand all',
|
'sessions.sidebar.header.displayMode.expandAll': 'Expand all',
|
||||||
|
'sessions.sidebar.header.projectSort.manual': 'Manual',
|
||||||
|
'sessions.sidebar.header.projectSort.aToZ': 'A → Z',
|
||||||
|
'sessions.sidebar.header.projectSort.zToA': 'Z → A',
|
||||||
|
'sessions.sidebar.header.projectSort.dateAdded': 'Newest',
|
||||||
|
'sessions.sidebar.header.projectSort.recent': 'Recent',
|
||||||
'sessions.sidebar.header.search.matchCountSingle': '{count} match',
|
'sessions.sidebar.header.search.matchCountSingle': '{count} match',
|
||||||
'sessions.sidebar.header.search.matchCountPlural': '{count} matches',
|
'sessions.sidebar.header.search.matchCountPlural': '{count} matches',
|
||||||
'sessions.sidebar.header.search.escapeHint': 'Esc to clear',
|
'sessions.sidebar.header.search.escapeHint': 'Esc to clear',
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"sessions.sidebar.header.actions.searchSessions": "Buscar sesiones",
|
"sessions.sidebar.header.actions.searchSessions": "Buscar sesiones",
|
||||||
"sessions.sidebar.header.actions.exitSelection": "Salir de selección",
|
"sessions.sidebar.header.actions.exitSelection": "Salir de selección",
|
||||||
"sessions.sidebar.header.actions.selectSessions": "Seleccionar sesiones",
|
"sessions.sidebar.header.actions.selectSessions": "Seleccionar sesiones",
|
||||||
|
"sessions.sidebar.header.actions.sortProjects": "Ordenar proyectos",
|
||||||
"sessions.sidebar.header.actions.sessionDisplayMode": "Modo de visualización de sesión",
|
"sessions.sidebar.header.actions.sessionDisplayMode": "Modo de visualización de sesión",
|
||||||
"sessions.sidebar.header.displayMode.label": "Modo de visualización",
|
"sessions.sidebar.header.displayMode.label": "Modo de visualización",
|
||||||
"sessions.sidebar.header.displayMode.default": "Predeterminado",
|
"sessions.sidebar.header.displayMode.default": "Predeterminado",
|
||||||
@@ -391,6 +392,11 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"sessions.sidebar.header.displayMode.showArchived": "Mostrar sesiones archivadas",
|
"sessions.sidebar.header.displayMode.showArchived": "Mostrar sesiones archivadas",
|
||||||
"sessions.sidebar.header.displayMode.collapseAll": "Colapsar todo",
|
"sessions.sidebar.header.displayMode.collapseAll": "Colapsar todo",
|
||||||
"sessions.sidebar.header.displayMode.expandAll": "Expandir todo",
|
"sessions.sidebar.header.displayMode.expandAll": "Expandir todo",
|
||||||
|
"sessions.sidebar.header.projectSort.manual": "Manual",
|
||||||
|
"sessions.sidebar.header.projectSort.aToZ": "A → Z",
|
||||||
|
"sessions.sidebar.header.projectSort.zToA": "Z → A",
|
||||||
|
"sessions.sidebar.header.projectSort.dateAdded": "Más recientes",
|
||||||
|
"sessions.sidebar.header.projectSort.recent": "Recientes",
|
||||||
"sessions.sidebar.header.search.matchCountSingle": "{count} coincidencia",
|
"sessions.sidebar.header.search.matchCountSingle": "{count} coincidencia",
|
||||||
"sessions.sidebar.header.search.matchCountPlural": "{count} coincidencias",
|
"sessions.sidebar.header.search.matchCountPlural": "{count} coincidencias",
|
||||||
"sessions.sidebar.header.search.escapeHint": "Esc para limpiar",
|
"sessions.sidebar.header.search.escapeHint": "Esc para limpiar",
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ export const dict = {
|
|||||||
'sessions.sidebar.header.actions.searchSessions': 'Sessions de recherche',
|
'sessions.sidebar.header.actions.searchSessions': 'Sessions de recherche',
|
||||||
'sessions.sidebar.header.actions.exitSelection': 'Quitter la sélection',
|
'sessions.sidebar.header.actions.exitSelection': 'Quitter la sélection',
|
||||||
'sessions.sidebar.header.actions.selectSessions': 'Sélectionnez des sessions',
|
'sessions.sidebar.header.actions.selectSessions': 'Sélectionnez des sessions',
|
||||||
|
'sessions.sidebar.header.actions.sortProjects': 'Trier les projets',
|
||||||
'sessions.sidebar.header.actions.sessionDisplayMode': 'Mode d\'affichage des sessions',
|
'sessions.sidebar.header.actions.sessionDisplayMode': 'Mode d\'affichage des sessions',
|
||||||
'sessions.sidebar.header.displayMode.label': 'Mode d\'affichage',
|
'sessions.sidebar.header.displayMode.label': 'Mode d\'affichage',
|
||||||
'sessions.sidebar.header.displayMode.default': 'Défaut',
|
'sessions.sidebar.header.displayMode.default': 'Défaut',
|
||||||
@@ -234,6 +235,11 @@ export const dict = {
|
|||||||
'sessions.sidebar.header.displayMode.showRecent': 'Afficher la section récente',
|
'sessions.sidebar.header.displayMode.showRecent': 'Afficher la section récente',
|
||||||
'sessions.sidebar.header.displayMode.collapseAll': 'Tout réduire',
|
'sessions.sidebar.header.displayMode.collapseAll': 'Tout réduire',
|
||||||
'sessions.sidebar.header.displayMode.expandAll': 'Tout développer',
|
'sessions.sidebar.header.displayMode.expandAll': 'Tout développer',
|
||||||
|
'sessions.sidebar.header.projectSort.manual': 'Manuel',
|
||||||
|
'sessions.sidebar.header.projectSort.aToZ': 'A → Z',
|
||||||
|
'sessions.sidebar.header.projectSort.zToA': 'Z → A',
|
||||||
|
'sessions.sidebar.header.projectSort.dateAdded': 'Les plus récentes',
|
||||||
|
'sessions.sidebar.header.projectSort.recent': 'Récentes',
|
||||||
'sessions.sidebar.header.search.matchCountSingle': 'Correspondance {count}',
|
'sessions.sidebar.header.search.matchCountSingle': 'Correspondance {count}',
|
||||||
'sessions.sidebar.header.search.matchCountPlural': 'Correspondances {count}',
|
'sessions.sidebar.header.search.matchCountPlural': 'Correspondances {count}',
|
||||||
'sessions.sidebar.header.search.escapeHint': 'Echap pour effacer',
|
'sessions.sidebar.header.search.escapeHint': 'Echap pour effacer',
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.actions.searchSessions': 'セッションを検索',
|
'sessions.sidebar.header.actions.searchSessions': 'セッションを検索',
|
||||||
'sessions.sidebar.header.actions.exitSelection': '選択を終了',
|
'sessions.sidebar.header.actions.exitSelection': '選択を終了',
|
||||||
'sessions.sidebar.header.actions.selectSessions': 'セッションを選択',
|
'sessions.sidebar.header.actions.selectSessions': 'セッションを選択',
|
||||||
|
'sessions.sidebar.header.actions.sortProjects': 'プロジェクトを並べ替え',
|
||||||
'sessions.sidebar.header.actions.sessionDisplayMode': 'セッション表示モード',
|
'sessions.sidebar.header.actions.sessionDisplayMode': 'セッション表示モード',
|
||||||
'sessions.sidebar.header.displayMode.label': '表示モード',
|
'sessions.sidebar.header.displayMode.label': '表示モード',
|
||||||
'sessions.sidebar.header.displayMode.default': 'デフォルト',
|
'sessions.sidebar.header.displayMode.default': 'デフォルト',
|
||||||
@@ -391,6 +392,11 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.displayMode.showArchived': 'アーカイブ済みセッションを表示',
|
'sessions.sidebar.header.displayMode.showArchived': 'アーカイブ済みセッションを表示',
|
||||||
'sessions.sidebar.header.displayMode.collapseAll': 'すべて折りたたむ',
|
'sessions.sidebar.header.displayMode.collapseAll': 'すべて折りたたむ',
|
||||||
'sessions.sidebar.header.displayMode.expandAll': 'すべて展開',
|
'sessions.sidebar.header.displayMode.expandAll': 'すべて展開',
|
||||||
|
'sessions.sidebar.header.projectSort.manual': '手動',
|
||||||
|
'sessions.sidebar.header.projectSort.aToZ': 'A → Z',
|
||||||
|
'sessions.sidebar.header.projectSort.zToA': 'Z → A',
|
||||||
|
'sessions.sidebar.header.projectSort.dateAdded': '新しい順',
|
||||||
|
'sessions.sidebar.header.projectSort.recent': '最近',
|
||||||
'sessions.sidebar.header.search.matchCountSingle': '{count}件一致',
|
'sessions.sidebar.header.search.matchCountSingle': '{count}件一致',
|
||||||
'sessions.sidebar.header.search.matchCountPlural': '{count}件一致',
|
'sessions.sidebar.header.search.matchCountPlural': '{count}件一致',
|
||||||
'sessions.sidebar.header.search.escapeHint': 'Escでクリア',
|
'sessions.sidebar.header.search.escapeHint': 'Escでクリア',
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.actions.searchSessions': '세션 검색',
|
'sessions.sidebar.header.actions.searchSessions': '세션 검색',
|
||||||
'sessions.sidebar.header.actions.exitSelection': '선택 종료',
|
'sessions.sidebar.header.actions.exitSelection': '선택 종료',
|
||||||
'sessions.sidebar.header.actions.selectSessions': '세션 선택',
|
'sessions.sidebar.header.actions.selectSessions': '세션 선택',
|
||||||
|
'sessions.sidebar.header.actions.sortProjects': '프로젝트 정렬',
|
||||||
'sessions.sidebar.header.actions.sessionDisplayMode': '세션 표시 모드',
|
'sessions.sidebar.header.actions.sessionDisplayMode': '세션 표시 모드',
|
||||||
'sessions.sidebar.header.displayMode.label': '표시 모드',
|
'sessions.sidebar.header.displayMode.label': '표시 모드',
|
||||||
'sessions.sidebar.header.displayMode.default': '기본값',
|
'sessions.sidebar.header.displayMode.default': '기본값',
|
||||||
@@ -391,6 +392,11 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.displayMode.showArchived': '보관된 세션 표시',
|
'sessions.sidebar.header.displayMode.showArchived': '보관된 세션 표시',
|
||||||
'sessions.sidebar.header.displayMode.collapseAll': '모두 접기',
|
'sessions.sidebar.header.displayMode.collapseAll': '모두 접기',
|
||||||
'sessions.sidebar.header.displayMode.expandAll': '모두 펼치기',
|
'sessions.sidebar.header.displayMode.expandAll': '모두 펼치기',
|
||||||
|
'sessions.sidebar.header.projectSort.manual': '수동',
|
||||||
|
'sessions.sidebar.header.projectSort.aToZ': 'A → Z',
|
||||||
|
'sessions.sidebar.header.projectSort.zToA': 'Z → A',
|
||||||
|
'sessions.sidebar.header.projectSort.dateAdded': '최신순',
|
||||||
|
'sessions.sidebar.header.projectSort.recent': '최근',
|
||||||
'sessions.sidebar.header.search.matchCountSingle': '{count}개 일치',
|
'sessions.sidebar.header.search.matchCountSingle': '{count}개 일치',
|
||||||
'sessions.sidebar.header.search.matchCountPlural': '{count}개 일치',
|
'sessions.sidebar.header.search.matchCountPlural': '{count}개 일치',
|
||||||
'sessions.sidebar.header.search.escapeHint': 'Esc로 지우기',
|
'sessions.sidebar.header.search.escapeHint': 'Esc로 지우기',
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.actions.searchSessions': 'Szukaj sesji',
|
'sessions.sidebar.header.actions.searchSessions': 'Szukaj sesji',
|
||||||
'sessions.sidebar.header.actions.exitSelection': 'Wyjdź z zaznaczenia',
|
'sessions.sidebar.header.actions.exitSelection': 'Wyjdź z zaznaczenia',
|
||||||
'sessions.sidebar.header.actions.selectSessions': 'Wybierz sesje',
|
'sessions.sidebar.header.actions.selectSessions': 'Wybierz sesje',
|
||||||
|
'sessions.sidebar.header.actions.sortProjects': 'Sortuj projekty',
|
||||||
'sessions.sidebar.header.actions.sessionDisplayMode': 'Tryb wyświetlania sesji',
|
'sessions.sidebar.header.actions.sessionDisplayMode': 'Tryb wyświetlania sesji',
|
||||||
'sessions.sidebar.header.displayMode.label': 'Tryb wyświetlania',
|
'sessions.sidebar.header.displayMode.label': 'Tryb wyświetlania',
|
||||||
'sessions.sidebar.header.displayMode.default': 'Domyślny',
|
'sessions.sidebar.header.displayMode.default': 'Domyślny',
|
||||||
@@ -213,6 +214,11 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.displayMode.showArchived': 'Pokaż zarchiwizowane sesje',
|
'sessions.sidebar.header.displayMode.showArchived': 'Pokaż zarchiwizowane sesje',
|
||||||
'sessions.sidebar.header.displayMode.collapseAll': 'Zwiń wszystkie',
|
'sessions.sidebar.header.displayMode.collapseAll': 'Zwiń wszystkie',
|
||||||
'sessions.sidebar.header.displayMode.expandAll': 'Rozwiń wszystkie',
|
'sessions.sidebar.header.displayMode.expandAll': 'Rozwiń wszystkie',
|
||||||
|
'sessions.sidebar.header.projectSort.manual': 'Ręcznie',
|
||||||
|
'sessions.sidebar.header.projectSort.aToZ': 'A → Z',
|
||||||
|
'sessions.sidebar.header.projectSort.zToA': 'Z → A',
|
||||||
|
'sessions.sidebar.header.projectSort.dateAdded': 'Najnowsze',
|
||||||
|
'sessions.sidebar.header.projectSort.recent': 'Ostatnie',
|
||||||
'sessions.sidebar.header.search.matchCountSingle': '{count} dopasowanie',
|
'sessions.sidebar.header.search.matchCountSingle': '{count} dopasowanie',
|
||||||
'sessions.sidebar.header.search.matchCountPlural': '{count} dopasowań',
|
'sessions.sidebar.header.search.matchCountPlural': '{count} dopasowań',
|
||||||
'sessions.sidebar.header.search.escapeHint': 'Esc aby wyczyścić',
|
'sessions.sidebar.header.search.escapeHint': 'Esc aby wyczyścić',
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"sessions.sidebar.header.actions.searchSessions": "Pesquisar sessões",
|
"sessions.sidebar.header.actions.searchSessions": "Pesquisar sessões",
|
||||||
"sessions.sidebar.header.actions.exitSelection": "Sair da seleção",
|
"sessions.sidebar.header.actions.exitSelection": "Sair da seleção",
|
||||||
"sessions.sidebar.header.actions.selectSessions": "Selecionar sessões",
|
"sessions.sidebar.header.actions.selectSessions": "Selecionar sessões",
|
||||||
|
"sessions.sidebar.header.actions.sortProjects": "Ordenar projetos",
|
||||||
"sessions.sidebar.header.actions.sessionDisplayMode": "Modo de visualização da sessão",
|
"sessions.sidebar.header.actions.sessionDisplayMode": "Modo de visualização da sessão",
|
||||||
"sessions.sidebar.header.displayMode.label": "Modo de visualização",
|
"sessions.sidebar.header.displayMode.label": "Modo de visualização",
|
||||||
"sessions.sidebar.header.displayMode.default": "Padrão",
|
"sessions.sidebar.header.displayMode.default": "Padrão",
|
||||||
@@ -391,6 +392,11 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"sessions.sidebar.header.displayMode.showArchived": "Mostrar sessões arquivadas",
|
"sessions.sidebar.header.displayMode.showArchived": "Mostrar sessões arquivadas",
|
||||||
"sessions.sidebar.header.displayMode.collapseAll": "Recolher tudo",
|
"sessions.sidebar.header.displayMode.collapseAll": "Recolher tudo",
|
||||||
"sessions.sidebar.header.displayMode.expandAll": "Expandir tudo",
|
"sessions.sidebar.header.displayMode.expandAll": "Expandir tudo",
|
||||||
|
"sessions.sidebar.header.projectSort.manual": "Manual",
|
||||||
|
"sessions.sidebar.header.projectSort.aToZ": "A → Z",
|
||||||
|
"sessions.sidebar.header.projectSort.zToA": "Z → A",
|
||||||
|
"sessions.sidebar.header.projectSort.dateAdded": "Mais recentes",
|
||||||
|
"sessions.sidebar.header.projectSort.recent": "Recentes",
|
||||||
"sessions.sidebar.header.search.matchCountSingle": "{count} correspondência",
|
"sessions.sidebar.header.search.matchCountSingle": "{count} correspondência",
|
||||||
"sessions.sidebar.header.search.matchCountPlural": "{count} correspondências",
|
"sessions.sidebar.header.search.matchCountPlural": "{count} correspondências",
|
||||||
"sessions.sidebar.header.search.escapeHint": "Esc para limpar",
|
"sessions.sidebar.header.search.escapeHint": "Esc para limpar",
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"sessions.sidebar.header.actions.searchSessions": "Пошук сесій",
|
"sessions.sidebar.header.actions.searchSessions": "Пошук сесій",
|
||||||
"sessions.sidebar.header.actions.exitSelection": "Вийти з вибору",
|
"sessions.sidebar.header.actions.exitSelection": "Вийти з вибору",
|
||||||
"sessions.sidebar.header.actions.selectSessions": "Вибрати сесії",
|
"sessions.sidebar.header.actions.selectSessions": "Вибрати сесії",
|
||||||
|
"sessions.sidebar.header.actions.sortProjects": "Сортувати проєкти",
|
||||||
"sessions.sidebar.header.actions.sessionDisplayMode": "Режим відображення сесії",
|
"sessions.sidebar.header.actions.sessionDisplayMode": "Режим відображення сесії",
|
||||||
"sessions.sidebar.header.displayMode.label": "Режим відображення",
|
"sessions.sidebar.header.displayMode.label": "Режим відображення",
|
||||||
"sessions.sidebar.header.displayMode.default": "За замовчуванням",
|
"sessions.sidebar.header.displayMode.default": "За замовчуванням",
|
||||||
@@ -391,6 +392,11 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"sessions.sidebar.header.displayMode.showArchived": "Показувати архівовані сесії",
|
"sessions.sidebar.header.displayMode.showArchived": "Показувати архівовані сесії",
|
||||||
"sessions.sidebar.header.displayMode.collapseAll": "Згорнути все",
|
"sessions.sidebar.header.displayMode.collapseAll": "Згорнути все",
|
||||||
"sessions.sidebar.header.displayMode.expandAll": "Розгорнути все",
|
"sessions.sidebar.header.displayMode.expandAll": "Розгорнути все",
|
||||||
|
"sessions.sidebar.header.projectSort.manual": "Вручну",
|
||||||
|
"sessions.sidebar.header.projectSort.aToZ": "A → Z",
|
||||||
|
"sessions.sidebar.header.projectSort.zToA": "Z → A",
|
||||||
|
"sessions.sidebar.header.projectSort.dateAdded": "Найновіші",
|
||||||
|
"sessions.sidebar.header.projectSort.recent": "Нещодавні",
|
||||||
"sessions.sidebar.header.search.matchCountSingle": "{count} збіг",
|
"sessions.sidebar.header.search.matchCountSingle": "{count} збіг",
|
||||||
"sessions.sidebar.header.search.matchCountPlural": "Збігів: {count}",
|
"sessions.sidebar.header.search.matchCountPlural": "Збігів: {count}",
|
||||||
"sessions.sidebar.header.search.escapeHint": "Esc, щоб очистити",
|
"sessions.sidebar.header.search.escapeHint": "Esc, щоб очистити",
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.actions.searchSessions': '搜索会话',
|
'sessions.sidebar.header.actions.searchSessions': '搜索会话',
|
||||||
'sessions.sidebar.header.actions.exitSelection': '退出选择',
|
'sessions.sidebar.header.actions.exitSelection': '退出选择',
|
||||||
'sessions.sidebar.header.actions.selectSessions': '选择会话',
|
'sessions.sidebar.header.actions.selectSessions': '选择会话',
|
||||||
|
'sessions.sidebar.header.actions.sortProjects': '排序项目',
|
||||||
'sessions.sidebar.header.actions.sessionDisplayMode': '会话显示模式',
|
'sessions.sidebar.header.actions.sessionDisplayMode': '会话显示模式',
|
||||||
'sessions.sidebar.header.displayMode.label': '显示模式',
|
'sessions.sidebar.header.displayMode.label': '显示模式',
|
||||||
'sessions.sidebar.header.displayMode.default': '默认',
|
'sessions.sidebar.header.displayMode.default': '默认',
|
||||||
@@ -391,6 +392,11 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.displayMode.showArchived': '显示已归档会话',
|
'sessions.sidebar.header.displayMode.showArchived': '显示已归档会话',
|
||||||
'sessions.sidebar.header.displayMode.collapseAll': '全部折叠',
|
'sessions.sidebar.header.displayMode.collapseAll': '全部折叠',
|
||||||
'sessions.sidebar.header.displayMode.expandAll': '全部展开',
|
'sessions.sidebar.header.displayMode.expandAll': '全部展开',
|
||||||
|
'sessions.sidebar.header.projectSort.manual': '手动',
|
||||||
|
'sessions.sidebar.header.projectSort.aToZ': 'A → Z',
|
||||||
|
'sessions.sidebar.header.projectSort.zToA': 'Z → A',
|
||||||
|
'sessions.sidebar.header.projectSort.dateAdded': '最新',
|
||||||
|
'sessions.sidebar.header.projectSort.recent': '最近',
|
||||||
'sessions.sidebar.header.search.matchCountSingle': '{count} 个匹配',
|
'sessions.sidebar.header.search.matchCountSingle': '{count} 个匹配',
|
||||||
'sessions.sidebar.header.search.matchCountPlural': '{count} 个匹配',
|
'sessions.sidebar.header.search.matchCountPlural': '{count} 个匹配',
|
||||||
'sessions.sidebar.header.search.escapeHint': '按 Esc 清除',
|
'sessions.sidebar.header.search.escapeHint': '按 Esc 清除',
|
||||||
|
|||||||
@@ -396,6 +396,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.actions.searchSessions': '搜尋會話',
|
'sessions.sidebar.header.actions.searchSessions': '搜尋會話',
|
||||||
'sessions.sidebar.header.actions.exitSelection': '退出選取',
|
'sessions.sidebar.header.actions.exitSelection': '退出選取',
|
||||||
'sessions.sidebar.header.actions.selectSessions': '選擇會話',
|
'sessions.sidebar.header.actions.selectSessions': '選擇會話',
|
||||||
|
'sessions.sidebar.header.actions.sortProjects': '排序專案',
|
||||||
'sessions.sidebar.header.actions.sessionDisplayMode': '會話顯示模式',
|
'sessions.sidebar.header.actions.sessionDisplayMode': '會話顯示模式',
|
||||||
'sessions.sidebar.header.displayMode.label': '顯示模式',
|
'sessions.sidebar.header.displayMode.label': '顯示模式',
|
||||||
'sessions.sidebar.header.displayMode.default': '預設',
|
'sessions.sidebar.header.displayMode.default': '預設',
|
||||||
@@ -404,6 +405,11 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.header.displayMode.showArchived': '顯示已封存會話',
|
'sessions.sidebar.header.displayMode.showArchived': '顯示已封存會話',
|
||||||
'sessions.sidebar.header.displayMode.collapseAll': '全部摺疊',
|
'sessions.sidebar.header.displayMode.collapseAll': '全部摺疊',
|
||||||
'sessions.sidebar.header.displayMode.expandAll': '全部展開',
|
'sessions.sidebar.header.displayMode.expandAll': '全部展開',
|
||||||
|
'sessions.sidebar.header.projectSort.manual': '手動',
|
||||||
|
'sessions.sidebar.header.projectSort.aToZ': 'A → Z',
|
||||||
|
'sessions.sidebar.header.projectSort.zToA': 'Z → A',
|
||||||
|
'sessions.sidebar.header.projectSort.dateAdded': '最新',
|
||||||
|
'sessions.sidebar.header.projectSort.recent': '最近',
|
||||||
'sessions.sidebar.header.search.matchCountSingle': '{count} 個符合',
|
'sessions.sidebar.header.search.matchCountSingle': '{count} 個符合',
|
||||||
'sessions.sidebar.header.search.matchCountPlural': '{count} 個符合',
|
'sessions.sidebar.header.search.matchCountPlural': '{count} 個符合',
|
||||||
'sessions.sidebar.header.search.escapeHint': '按 Esc 清除',
|
'sessions.sidebar.header.search.escapeHint': '按 Esc 清除',
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ interface VSCodeWorkspaceFolderConfig {
|
|||||||
interface ProjectsStore {
|
interface ProjectsStore {
|
||||||
projects: ProjectEntry[];
|
projects: ProjectEntry[];
|
||||||
activeProjectId: string | null;
|
activeProjectId: string | null;
|
||||||
|
manualProjectOrder: string[];
|
||||||
|
|
||||||
addProject: (path: string, options?: { label?: string; id?: string }) => ProjectEntry | null;
|
addProject: (path: string, options?: { label?: string; id?: string }) => ProjectEntry | null;
|
||||||
removeProject: (id: string) => void;
|
removeProject: (id: string) => void;
|
||||||
@@ -315,6 +316,17 @@ const readPersistedProjects = (): ProjectEntry[] => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const readPersistedManualOrder = (): string[] => {
|
||||||
|
try {
|
||||||
|
const raw = safeStorage.getItem(getProjectsStorageKey() + ':manualOrder');
|
||||||
|
if (!raw) return [];
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
return Array.isArray(parsed) ? parsed.filter((id): id is string => typeof id === 'string') : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const readPersistedActiveProjectId = (): string | null => {
|
const readPersistedActiveProjectId = (): string | null => {
|
||||||
try {
|
try {
|
||||||
const raw = safeStorage.getItem(getActiveProjectStorageKey())
|
const raw = safeStorage.getItem(getActiveProjectStorageKey())
|
||||||
@@ -347,11 +359,22 @@ const cacheProjects = (projects: ProjectEntry[], activeProjectId: string | null)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const persistProjects = (projects: ProjectEntry[], activeProjectId: string | null) => {
|
const persistProjects = (projects: ProjectEntry[], activeProjectId: string | null, manualOrder?: string[]) => {
|
||||||
cacheProjects(projects, activeProjectId);
|
cacheProjects(projects, activeProjectId);
|
||||||
|
if (manualOrder) {
|
||||||
|
persistManualProjectOrder(manualOrder);
|
||||||
|
}
|
||||||
void updateDesktopSettings({ projects, activeProjectId: activeProjectId ?? undefined });
|
void updateDesktopSettings({ projects, activeProjectId: activeProjectId ?? undefined });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const persistManualProjectOrder = (manualOrder: string[]) => {
|
||||||
|
try {
|
||||||
|
safeStorage.setItem(getProjectsStorageKey() + ':manualOrder', JSON.stringify(manualOrder));
|
||||||
|
} catch {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const initialProjects = readPersistedProjects();
|
const initialProjects = readPersistedProjects();
|
||||||
const normalizeVSCodeWorkspaceFolders = (folders: VSCodeWorkspaceFolderConfig[]): VSCodeWorkspaceFolderConfig[] => {
|
const normalizeVSCodeWorkspaceFolders = (folders: VSCodeWorkspaceFolderConfig[]): VSCodeWorkspaceFolderConfig[] => {
|
||||||
const result: VSCodeWorkspaceFolderConfig[] = [];
|
const result: VSCodeWorkspaceFolderConfig[] = [];
|
||||||
@@ -536,6 +559,7 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
devtools((set, get) => ({
|
devtools((set, get) => ({
|
||||||
projects: effectiveInitialProjects,
|
projects: effectiveInitialProjects,
|
||||||
activeProjectId: initialActiveProjectId,
|
activeProjectId: initialActiveProjectId,
|
||||||
|
manualProjectOrder: readPersistedManualOrder(),
|
||||||
|
|
||||||
validateProjectPath: (path: string): ProjectPathValidationResult => {
|
validateProjectPath: (path: string): ProjectPathValidationResult => {
|
||||||
if (typeof path !== 'string' || path.trim().length === 0) {
|
if (typeof path !== 'string' || path.trim().length === 0) {
|
||||||
@@ -604,8 +628,9 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
nextActiveId = nextProjects[0]?.id ?? null;
|
nextActiveId = nextProjects[0]?.id ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
set({ projects: nextProjects, activeProjectId: nextActiveId });
|
const nextManualOrder = get().manualProjectOrder.filter((oid) => oid !== id);
|
||||||
persistProjects(nextProjects, nextActiveId);
|
set({ projects: nextProjects, activeProjectId: nextActiveId, manualProjectOrder: nextManualOrder });
|
||||||
|
persistProjects(nextProjects, nextActiveId, nextManualOrder);
|
||||||
|
|
||||||
// Clean up worktree entries for the removed project
|
// Clean up worktree entries for the removed project
|
||||||
if (project) {
|
if (project) {
|
||||||
@@ -647,7 +672,7 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
);
|
);
|
||||||
|
|
||||||
set({ projects: nextProjects, activeProjectId: id });
|
set({ projects: nextProjects, activeProjectId: id });
|
||||||
persistProjects(nextProjects, id);
|
persistProjects(nextProjects, id, get().manualProjectOrder);
|
||||||
|
|
||||||
opencodeClient.setDirectory(target.path);
|
opencodeClient.setDirectory(target.path);
|
||||||
useDirectoryStore.getState().setDirectory(target.path, { showOverlay: false });
|
useDirectoryStore.getState().setDirectory(target.path, { showOverlay: false });
|
||||||
@@ -672,7 +697,7 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
);
|
);
|
||||||
|
|
||||||
set({ projects: nextProjects, activeProjectId: id });
|
set({ projects: nextProjects, activeProjectId: id });
|
||||||
persistProjects(nextProjects, id);
|
persistProjects(nextProjects, id, get().manualProjectOrder);
|
||||||
},
|
},
|
||||||
|
|
||||||
renameProject: (id: string, label: string) => {
|
renameProject: (id: string, label: string) => {
|
||||||
@@ -689,7 +714,7 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
project.id === id ? { ...project, label: trimmed } : project
|
project.id === id ? { ...project, label: trimmed } : project
|
||||||
);
|
);
|
||||||
set({ projects: nextProjects });
|
set({ projects: nextProjects });
|
||||||
persistProjects(nextProjects, activeProjectId);
|
persistProjects(nextProjects, activeProjectId, get().manualProjectOrder);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateProjectMeta: (id: string, meta: {
|
updateProjectMeta: (id: string, meta: {
|
||||||
@@ -726,7 +751,7 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
set({ projects: nextProjects });
|
set({ projects: nextProjects });
|
||||||
persistProjects(nextProjects, activeProjectId);
|
persistProjects(nextProjects, activeProjectId, get().manualProjectOrder);
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadProjectIcon: async (id: string, file: File) => {
|
uploadProjectIcon: async (id: string, file: File) => {
|
||||||
@@ -863,8 +888,9 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
const [moved] = nextProjects.splice(fromIndex, 1);
|
const [moved] = nextProjects.splice(fromIndex, 1);
|
||||||
nextProjects.splice(toIndex, 0, moved);
|
nextProjects.splice(toIndex, 0, moved);
|
||||||
|
|
||||||
set({ projects: nextProjects });
|
const newOrder = nextProjects.map((p) => p.id);
|
||||||
persistProjects(nextProjects, activeProjectId);
|
set({ projects: nextProjects, manualProjectOrder: newOrder });
|
||||||
|
persistProjects(nextProjects, activeProjectId, newOrder);
|
||||||
},
|
},
|
||||||
|
|
||||||
resetForRuntimeSwitch: () => {
|
resetForRuntimeSwitch: () => {
|
||||||
@@ -876,7 +902,7 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
const nextActiveProjectId = projects.some((project) => project.id === activeProjectId)
|
const nextActiveProjectId = projects.some((project) => project.id === activeProjectId)
|
||||||
? activeProjectId
|
? activeProjectId
|
||||||
: projects[0]?.id ?? null;
|
: projects[0]?.id ?? null;
|
||||||
set({ projects, activeProjectId: nextActiveProjectId });
|
set({ projects, activeProjectId: nextActiveProjectId, manualProjectOrder: [] });
|
||||||
},
|
},
|
||||||
|
|
||||||
synchronizeFromSettings: (settings: DesktopSettings) => {
|
synchronizeFromSettings: (settings: DesktopSettings) => {
|
||||||
@@ -903,6 +929,7 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
if (activeExists) {
|
if (activeExists) {
|
||||||
set({ activeProjectId: incomingActive });
|
set({ activeProjectId: incomingActive });
|
||||||
cacheProjects(current.projects, incomingActive);
|
cacheProjects(current.projects, incomingActive);
|
||||||
|
persistManualProjectOrder(get().manualProjectOrder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -915,8 +942,11 @@ export const useProjectsStore = create<ProjectsStore>()(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
set({ projects: incomingProjects, activeProjectId: incomingActive });
|
const incomingIds = new Set(incomingProjects.map((p) => p.id));
|
||||||
|
const cleanedOrder = get().manualProjectOrder.filter((id) => incomingIds.has(id));
|
||||||
|
set({ projects: incomingProjects, activeProjectId: incomingActive, manualProjectOrder: cleanedOrder });
|
||||||
cacheProjects(incomingProjects, incomingActive);
|
cacheProjects(incomingProjects, incomingActive);
|
||||||
|
persistManualProjectOrder(cleanedOrder);
|
||||||
|
|
||||||
if (incomingActive) {
|
if (incomingActive) {
|
||||||
const activeProject = incomingProjects.find((project) => project.id === incomingActive);
|
const activeProject = incomingProjects.find((project) => project.id === incomingActive);
|
||||||
|
|||||||
@@ -3,15 +3,19 @@ import { persist } from 'zustand/middleware';
|
|||||||
|
|
||||||
type SessionDisplayMode = 'default' | 'minimal';
|
type SessionDisplayMode = 'default' | 'minimal';
|
||||||
|
|
||||||
|
type ProjectSortOrder = 'manual' | 'a-z' | 'z-a' | 'date-added' | 'recent';
|
||||||
|
|
||||||
type SessionDisplayStore = {
|
type SessionDisplayStore = {
|
||||||
displayMode: SessionDisplayMode;
|
displayMode: SessionDisplayMode;
|
||||||
showRecentSection: boolean;
|
showRecentSection: boolean;
|
||||||
showArchivedSessions: boolean;
|
showArchivedSessions: boolean;
|
||||||
|
projectSortOrder: ProjectSortOrder;
|
||||||
setDisplayMode: (mode: SessionDisplayMode) => void;
|
setDisplayMode: (mode: SessionDisplayMode) => void;
|
||||||
setShowRecentSection: (show: boolean) => void;
|
setShowRecentSection: (show: boolean) => void;
|
||||||
setShowArchivedSessions: (show: boolean) => void;
|
setShowArchivedSessions: (show: boolean) => void;
|
||||||
toggleRecentSection: () => void;
|
toggleRecentSection: () => void;
|
||||||
toggleArchivedSessions: () => void;
|
toggleArchivedSessions: () => void;
|
||||||
|
setProjectSortOrder: (order: ProjectSortOrder) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSessionDisplayStore = create<SessionDisplayStore>()(
|
export const useSessionDisplayStore = create<SessionDisplayStore>()(
|
||||||
@@ -24,25 +28,33 @@ export const useSessionDisplayStore = create<SessionDisplayStore>()(
|
|||||||
// disappear once the persisted preference rehydrates. Users who opted into
|
// disappear once the persisted preference rehydrates. Users who opted into
|
||||||
// showing archived have `true` persisted, which is preserved on rehydrate.
|
// showing archived have `true` persisted, which is preserved on rehydrate.
|
||||||
showArchivedSessions: false,
|
showArchivedSessions: false,
|
||||||
|
projectSortOrder: 'recent',
|
||||||
setDisplayMode: (mode) => set({ displayMode: mode }),
|
setDisplayMode: (mode) => set({ displayMode: mode }),
|
||||||
setShowRecentSection: (show) => set({ showRecentSection: show }),
|
setShowRecentSection: (show) => set({ showRecentSection: show }),
|
||||||
setShowArchivedSessions: (show) => set({ showArchivedSessions: show }),
|
setShowArchivedSessions: (show) => set({ showArchivedSessions: show }),
|
||||||
toggleRecentSection: () => set((state) => ({ showRecentSection: !state.showRecentSection })),
|
toggleRecentSection: () => set((state) => ({ showRecentSection: !state.showRecentSection })),
|
||||||
toggleArchivedSessions: () => set((state) => ({ showArchivedSessions: !state.showArchivedSessions })),
|
toggleArchivedSessions: () => set((state) => ({ showArchivedSessions: !state.showArchivedSessions })),
|
||||||
|
setProjectSortOrder: (order) => set({ projectSortOrder: order }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'session-display-mode',
|
name: 'session-display-mode',
|
||||||
version: 1,
|
version: 2,
|
||||||
// v0 shipped 'default' as the only/initial mode, so most existing users
|
// v0 shipped 'default' as the only/initial mode, so most existing users
|
||||||
// have it persisted by accident rather than choice. Nudge everyone onto
|
// have it persisted by accident rather than choice. Nudge everyone onto
|
||||||
// minimal once so the mode can be evaluated before removing it entirely.
|
// minimal once so the mode can be evaluated before removing it entirely.
|
||||||
|
// v1→v2 adds projectSortOrder defaulting to 'recent'.
|
||||||
migrate: (persisted, version) => {
|
migrate: (persisted, version) => {
|
||||||
const state = (persisted ?? {}) as Partial<SessionDisplayStore>;
|
const state = (persisted ?? {}) as Partial<SessionDisplayStore>;
|
||||||
if (version < 1) {
|
if (version < 1) {
|
||||||
return { ...state, displayMode: 'minimal' };
|
return { ...state, displayMode: 'minimal', projectSortOrder: 'recent' };
|
||||||
|
}
|
||||||
|
if (version < 2) {
|
||||||
|
return { ...state, projectSortOrder: 'recent' };
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type { ProjectSortOrder };
|
||||||
|
|||||||
Reference in New Issue
Block a user