From 0a876c7cf2b3c74a416e666fba5ce6cab090bdf4 Mon Sep 17 00:00:00 2001 From: 1shCha Date: Tue, 10 Feb 2026 01:19:48 -0500 Subject: [PATCH 1/3] iteration1 --- src/components/layout/DashboardLayout.tsx | 14 +- src/components/modals/ModalManager.tsx | 113 +++- .../workspace-canvas/WorkspaceCard.tsx | 11 + .../workspace-canvas/WorkspaceContent.tsx | 10 +- src/lib/stores/ui-store.ts | 639 ++++++++++-------- 5 files changed, 465 insertions(+), 322 deletions(-) diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx index d22ae81..78d33be 100644 --- a/src/components/layout/DashboardLayout.tsx +++ b/src/components/layout/DashboardLayout.tsx @@ -8,6 +8,7 @@ import { WorkspaceCanvasDropzone } from "@/components/workspace-canvas/Workspace import { AssistantDropzone } from "@/components/assistant-ui/AssistantDropzone"; import { PANEL_DEFAULTS } from "@/lib/layout-constants"; import React, { useCallback, useEffect, useRef } from "react"; +import { useUIStore } from "@/lib/stores/ui-store"; interface DashboardLayoutProps { // Workspace sidebar @@ -69,6 +70,10 @@ export function DashboardLayout({ maximizedItemId, workspaceSplitViewActive = false, }: DashboardLayoutProps) { + // Subscribe to openPanelIds for dual-panel mode detection + const openPanelIds = useUIStore((state) => state.openPanelIds); + const isDualPanel = workspaceSplitViewActive && openPanelIds.length === 2; + // Get sidebar control to auto-close when panels open const { setOpen } = useSidebar(); @@ -158,8 +163,13 @@ export function DashboardLayout({ - {/* WORKSPACE SPLIT VIEW MODE: Show workspace + item side-by-side */} - {workspaceSplitViewActive && maximizedItemId ? ( + {/* DUAL-PANEL MODE: Show two item panels side-by-side */} + {isDualPanel ? ( + + {modalManager} + + ) : workspaceSplitViewActive && maximizedItemId ? ( + /* WORKSPACE SPLIT VIEW MODE: Show workspace + item side-by-side */ {/* Workspace Panel - Single Column Mode */} - {/* Card Detail Modal or PDF Viewer Modal - only shown when item is maximized */} - {activeItemId && currentItem && maximizedItemId === currentItem.id && ( - currentItem.type === 'pdf' ? ( - handleClose(currentItem.id)} - onUpdateItem={(updates) => onUpdateItem(currentItem.id, updates)} - renderInline={workspaceSplitViewActive} - /> - ) : ( - handleClose(currentItem.id)} - onUpdateItem={(updates) => onUpdateItem(currentItem.id, updates)} - onUpdateItemData={(updater) => onUpdateItemData(currentItem.id, updater)} - onFlushPendingChanges={onFlushPendingChanges} - renderInline={workspaceSplitViewActive} - /> + {/* Dual-Panel Mode: Render both panels when openPanelIds.length === 2 and split view is active */} + {workspaceSplitViewActive && openPanelIds.length === 2 ? ( + <> + {/* First Panel (Left) */} + + {openPanelIds[0] && (() => { + const item1 = items.find(i => i.id === openPanelIds[0]); + if (!item1) return null; + + return item1.type === 'pdf' ? ( + handleClose(item1.id)} + onUpdateItem={(updates) => onUpdateItem(item1.id, updates)} + renderInline={true} + /> + ) : ( + handleClose(item1.id)} + onUpdateItem={(updates) => onUpdateItem(item1.id, updates)} + onUpdateItemData={(updater) => onUpdateItemData(item1.id, updater)} + onFlushPendingChanges={onFlushPendingChanges} + renderInline={true} + /> + ); + })()} + + + + + {/* Second Panel (Right) */} + + {openPanelIds[1] && (() => { + const item2 = items.find(i => i.id === openPanelIds[1]); + if (!item2) return null; + + return item2.type === 'pdf' ? ( + handleClose(item2.id)} + onUpdateItem={(updates) => onUpdateItem(item2.id, updates)} + renderInline={true} + /> + ) : ( + handleClose(item2.id)} + onUpdateItem={(updates) => onUpdateItem(item2.id, updates)} + onUpdateItemData={(updater) => onUpdateItemData(item2.id, updater)} + onFlushPendingChanges={onFlushPendingChanges} + renderInline={true} + /> + ); + })()} + + + ) : ( + /* Single Panel Mode: Card Detail Modal or PDF Viewer Modal - only shown when item is maximized */ + activeItemId && currentItem && maximizedItemId === currentItem.id && ( + currentItem.type === 'pdf' ? ( + handleClose(currentItem.id)} + onUpdateItem={(updates) => onUpdateItem(currentItem.id, updates)} + renderInline={workspaceSplitViewActive} + /> + ) : ( + handleClose(currentItem.id)} + onUpdateItem={(updates) => onUpdateItem(currentItem.id, updates)} + onUpdateItemData={(updater) => onUpdateItemData(currentItem.id, updater)} + onFlushPendingChanges={onFlushPendingChanges} + renderInline={workspaceSplitViewActive} + /> + ) ) )} @@ -105,4 +173,3 @@ export function ModalManager({ ); } - diff --git a/src/components/workspace-canvas/WorkspaceCard.tsx b/src/components/workspace-canvas/WorkspaceCard.tsx index 410ec34..77ab39b 100644 --- a/src/components/workspace-canvas/WorkspaceCard.tsx +++ b/src/components/workspace-canvas/WorkspaceCard.tsx @@ -222,6 +222,7 @@ function WorkspaceCard({ const setOpenModalItemId = useUIStore((state) => state.setOpenModalItemId); const openPanel = useUIStore((state) => state.openPanel); const closePanel = useUIStore((state) => state.closePanel); + const workspaceSplitViewActive = useUIStore((state) => state.workspaceSplitViewActive); // Register this card's minimal context (title, id, type) with the assistant useCardContextProvider(item); @@ -1083,6 +1084,16 @@ function WorkspaceCard({ Change Color + {/* Double Panel Option - only show when in split view with exactly 1 panel open */} + {workspaceSplitViewActive && openPanelIds.length === 1 && !openPanelIds.includes(item.id) && ( + <> + + openPanel(item.id, 'dual')}> + + Double Panel + + + )} state.workspaceSplitViewActive); const maximizedItemId = useUIStore((state) => state.maximizedItemId); + const openPanelIds = useUIStore((state) => state.openPanelIds); + @@ -144,12 +146,14 @@ export default function WorkspaceContent({ // In workspace split view mode, exclude the currently maximized item from the grid // so it only appears in the editor panel, not in both places - if (workspaceSplitViewActive && maximizedItemId) { - return filtered.filter(item => item.id !== maximizedItemId); + // In workspace split view mode, exclude open panels from the grid + // so they only appear in the panel area, not in both places + if (workspaceSplitViewActive && openPanelIds.length > 0) { + return filtered.filter(item => !openPanelIds.includes(item.id)); } return filtered; - }, [viewState.items, searchQuery, activeFolderId, workspaceSplitViewActive, maximizedItemId]); + }, [viewState.items, searchQuery, activeFolderId, workspaceSplitViewActive, openPanelIds]); // Handle opening a folder (folders are now items with type: 'folder') const handleOpenFolder = useCallback((folderId: string) => { diff --git a/src/lib/stores/ui-store.ts b/src/lib/stores/ui-store.ts index f959a6e..117cab5 100644 --- a/src/lib/stores/ui-store.ts +++ b/src/lib/stores/ui-store.ts @@ -66,7 +66,7 @@ interface UIState { // Actions - Panels - openPanel: (itemId: string, mode: 'replace' | 'add') => void; + openPanel: (itemId: string, mode: 'replace' | 'add' | 'dual') => void; closePanel: (itemId: string) => void; closeAllPanels: () => void; reorderPanels: (fromIndex: number, toIndex: number) => void; @@ -185,346 +185,396 @@ const initialState = { export const useUIStore = create()( devtools( persist( - (set) => ({ - ...initialState, - - // Folder navigation — preserve manual user selections - setActiveFolderId: (folderId) => { - set((state) => { - if (state.activeFolderId === folderId && state.openPanelIds.length === 0) { - return {}; - } - // Remove only auto-selected cards, preserve manual selections - const newSelectedCardIds = new Set(state.selectedCardIds); - state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); - return { - activeFolderId: folderId, - openPanelIds: [], - maximizedItemId: null, - selectedCardIds: newSelectedCardIds, - panelAutoSelectedCardIds: new Set(), - }; - }); - }, - - clearActiveFolder: () => { - set((state) => { - if (state.activeFolderId === null && state.openPanelIds.length === 0) return {}; - // Remove only auto-selected cards, preserve manual selections - const newSelectedCardIds = new Set(state.selectedCardIds); - state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); - return { - activeFolderId: null, - openPanelIds: [], - maximizedItemId: null, - selectedCardIds: newSelectedCardIds, - panelAutoSelectedCardIds: new Set(), - }; - }); - }, - - // Direct setter used by useFolderUrl hook for URL → store sync - // Does NOT clear panels — only updates the folder ID - _setActiveFolderIdDirect: (folderId) => set({ activeFolderId: folderId }), - - // Direct panel open/close used by useFolderUrl hook for URL → store sync - _openPanelDirect: (itemId) => set({ openPanelIds: [itemId], maximizedItemId: itemId }), - _closePanelDirect: () => set((state) => { - // Remove only auto-selected cards, preserve manual selections - const newSelectedCardIds = new Set(state.selectedCardIds); - state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); - return { - openPanelIds: [], - maximizedItemId: null, - selectedCardIds: newSelectedCardIds, - panelAutoSelectedCardIds: new Set(), - }; - }), - - // Panel actions - openPanel: (itemId, mode) => { - set((state) => { - const isAlreadyOpen = state.openPanelIds.length === 1 && state.openPanelIds[0] === itemId; - if (isAlreadyOpen) return {}; - - const newSelectedCardIds = new Set(state.selectedCardIds); - const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); + (set) => ({ + ...initialState, + + // Folder navigation — preserve manual user selections + setActiveFolderId: (folderId) => { + set((state) => { + if (state.activeFolderId === folderId && state.openPanelIds.length === 0) { + return {}; + } + // Remove only auto-selected cards, preserve manual selections + const newSelectedCardIds = new Set(state.selectedCardIds); + state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); + return { + activeFolderId: folderId, + openPanelIds: [], + maximizedItemId: null, + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: new Set(), + }; + }); + }, - // Add to selections and track as auto-selected - newSelectedCardIds.add(itemId); - newPanelAutoSelectedCardIds.add(itemId); + clearActiveFolder: () => { + set((state) => { + if (state.activeFolderId === null && state.openPanelIds.length === 0) return {}; + // Remove only auto-selected cards, preserve manual selections + const newSelectedCardIds = new Set(state.selectedCardIds); + state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); + return { + activeFolderId: null, + openPanelIds: [], + maximizedItemId: null, + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: new Set(), + }; + }); + }, - return { - openPanelIds: [itemId], - maximizedItemId: itemId, - selectedCardIds: newSelectedCardIds, - panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, - }; - }); - }, + // Direct setter used by useFolderUrl hook for URL → store sync + // Does NOT clear panels — only updates the folder ID + _setActiveFolderIdDirect: (folderId) => set({ activeFolderId: folderId }), - closePanel: (itemId) => { - set((state) => { - if (state.openPanelIds.length === 0) return {}; + // Direct panel open/close used by useFolderUrl hook for URL → store sync + _openPanelDirect: (itemId) => set({ openPanelIds: [itemId], maximizedItemId: itemId }), + _closePanelDirect: () => set((state) => { // Remove only auto-selected cards, preserve manual selections const newSelectedCardIds = new Set(state.selectedCardIds); state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); return { openPanelIds: [], maximizedItemId: null, - workspaceSplitViewActive: false, // Disable split view when closing selectedCardIds: newSelectedCardIds, panelAutoSelectedCardIds: new Set(), }; - }); - }, + }), - closeAllPanels: () => { - set((state) => { - if (state.openPanelIds.length === 0) return {}; - // Remove only auto-selected cards, preserve manual selections - const newSelectedCardIds = new Set(state.selectedCardIds); - state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); - return { - openPanelIds: [], - maximizedItemId: null, - selectedCardIds: newSelectedCardIds, - panelAutoSelectedCardIds: new Set(), - }; - }); - }, + // Panel actions + openPanel: (itemId, mode) => { + set((state) => { + const isAlreadyOpen = state.openPanelIds.includes(itemId); + if (isAlreadyOpen) return {}; - reorderPanels: (fromIndex, toIndex) => set((state) => { - // No reordering in single view - return state; - }), + const newSelectedCardIds = new Set(state.selectedCardIds); + const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); - setItemPrompt: (prompt) => set({ itemPrompt: prompt }), - setMaximizedItemId: (id) => set((state) => { - // Disable workspace split view when item is un-maximized - if (id === null && state.workspaceSplitViewActive) { - return { maximizedItemId: id, workspaceSplitViewActive: false }; - } + // Add to selections and track as auto-selected + newSelectedCardIds.add(itemId); + newPanelAutoSelectedCardIds.add(itemId); + + // Handle different modes + if (mode === 'dual') { + // Dual-panel mode: add as second panel (only if we have exactly 1 panel open) + if (state.openPanelIds.length === 1) { + return { + openPanelIds: [itemId, state.openPanelIds[0]], // New panel becomes left (index 0) + maximizedItemId: itemId, // Focus on the newly opened panel + workspaceSplitViewActive: true, // Enable split view for dual-panel + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, + }; + } + // If not exactly 1 panel open, fall through to replace mode + } - // In workspace split view, deselect the previously maximized card when switching to a new one - if (state.workspaceSplitViewActive && id !== null && state.maximizedItemId !== id) { - const newSelectedCardIds = new Set(state.selectedCardIds); - if (state.maximizedItemId) { - newSelectedCardIds.delete(state.maximizedItemId); - } - return { - maximizedItemId: id, - selectedCardIds: newSelectedCardIds - }; - } + // Replace mode (default): replace all open panels with this one + return { + openPanelIds: [itemId], + maximizedItemId: itemId, + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, + }; + }); + }, - return { maximizedItemId: id }; - }), + closePanel: (itemId) => { + set((state) => { + if (state.openPanelIds.length === 0) return {}; - // Workspace Split View actions - // Workspace Split View actions - toggleWorkspaceSplitView: () => set((state) => { - // Only allow enabling split view if an item is maximized - if (!state.workspaceSplitViewActive && !state.maximizedItemId) { - return { workspaceSplitViewActive: false }; - } - return { workspaceSplitViewActive: !state.workspaceSplitViewActive }; - }), - enableWorkspaceSplitView: () => set((state) => { - if (!state.maximizedItemId) return { workspaceSplitViewActive: false }; - return { workspaceSplitViewActive: true }; - }), - disableWorkspaceSplitView: () => set({ workspaceSplitViewActive: false }), + // Dual-panel mode: closing one panel should keep the other + if (state.openPanelIds.length === 2) { + const remainingPanelId = state.openPanelIds.find(id => id !== itemId); + if (!remainingPanelId) { + // Shouldn't happen, but fallback to closing all + const newSelectedCardIds = new Set(state.selectedCardIds); + state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); + return { + openPanelIds: [], + maximizedItemId: null, + workspaceSplitViewActive: false, + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: new Set(), + }; + } + + // Remove the closed panel from selections but keep the remaining one + const newSelectedCardIds = new Set(state.selectedCardIds); + const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); + if (newPanelAutoSelectedCardIds.has(itemId)) { + newSelectedCardIds.delete(itemId); + newPanelAutoSelectedCardIds.delete(itemId); + } + + return { + openPanelIds: [remainingPanelId], + maximizedItemId: remainingPanelId, + workspaceSplitViewActive: true, // Keep split view active with single panel + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, + }; + } - // Legacy compatibility - setOpenModalItemId: (id) => { - set((state) => { - if (id === null) { - if (state.openPanelIds.length === 0) return {}; + // Single panel mode: close all panels + const newSelectedCardIds = new Set(state.selectedCardIds); + state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); return { openPanelIds: [], maximizedItemId: null, + workspaceSplitViewActive: false, // Disable split view when closing + selectedCardIds: newSelectedCardIds, panelAutoSelectedCardIds: new Set(), }; - } else { - const isAlreadyOpen = state.openPanelIds.length === 1 && state.openPanelIds[0] === id; - if (isAlreadyOpen) return {}; + }); + }, + closeAllPanels: () => { + set((state) => { + if (state.openPanelIds.length === 0) return {}; + // Remove only auto-selected cards, preserve manual selections const newSelectedCardIds = new Set(state.selectedCardIds); - const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); + state.panelAutoSelectedCardIds.forEach(id => newSelectedCardIds.delete(id)); + return { + openPanelIds: [], + maximizedItemId: null, + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: new Set(), + }; + }); + }, + + reorderPanels: (fromIndex, toIndex) => set((state) => { + // No reordering in single view + return state; + }), + + setItemPrompt: (prompt) => set({ itemPrompt: prompt }), + setMaximizedItemId: (id) => set((state) => { + // Disable workspace split view when item is un-maximized + if (id === null && state.workspaceSplitViewActive) { + return { maximizedItemId: id, workspaceSplitViewActive: false }; + } - // In workspace split view, deselect the previously maximized card when opening a new one - if (state.workspaceSplitViewActive && state.maximizedItemId && state.maximizedItemId !== id) { + // In workspace split view, deselect the previously maximized card when switching to a new one + if (state.workspaceSplitViewActive && id !== null && state.maximizedItemId !== id) { + const newSelectedCardIds = new Set(state.selectedCardIds); + if (state.maximizedItemId) { newSelectedCardIds.delete(state.maximizedItemId); - newPanelAutoSelectedCardIds.delete(state.maximizedItemId); } - - // Add to selections and track as auto-selected - newSelectedCardIds.add(id); - newPanelAutoSelectedCardIds.add(id); - return { - openPanelIds: [id], maximizedItemId: id, - selectedCardIds: newSelectedCardIds, - panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, + selectedCardIds: newSelectedCardIds }; } - }); - }, - - setShowVersionHistory: (show) => set({ showVersionHistory: show }), - setShowCreateWorkspaceModal: (show) => set({ showCreateWorkspaceModal: show }), - setShowSheetModal: (show) => set({ showSheetModal: show }), - - // UI Preferences actions - setShowJsonView: (show) => set({ showJsonView: show }), - setSearchQuery: (query) => set({ searchQuery: query }), - - setSelectedActions: (actions) => set({ selectedActions: actions }), - clearSelectedActions: () => set({ selectedActions: [] }), - setSelectedModelId: (modelId) => set({ selectedModelId: modelId }), - - // Text selection actions - setInMultiSelectMode: (inMultiMode) => set({ inMultiSelectMode: inMultiMode }), - setInSingleSelectMode: (inSingleMode) => set({ inSingleSelectMode: inSingleMode }), - setTooltipVisible: (visible) => set({ tooltipVisible: visible }), - setSelectedHighlightColorId: (colorId) => set({ selectedHighlightColorId: colorId }), - enterMultiSelectMode: () => set({ inMultiSelectMode: true, inSingleSelectMode: false, tooltipVisible: true }), - exitMultiSelectMode: () => set({ inMultiSelectMode: false, tooltipVisible: false }), - enterSingleSelectMode: () => set({ inSingleSelectMode: true, inMultiSelectMode: false, tooltipVisible: true }), - exitSingleSelectMode: () => set({ inSingleSelectMode: false, tooltipVisible: false }), - - // Card selection actions - toggleCardSelection: (id) => set((state) => { - const newSet = new Set(state.selectedCardIds); - const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); - - if (newSet.has(id)) { - newSet.delete(id); - newPanelAutoSelectedCardIds.delete(id); - } else { - newSet.add(id); - } - return { - selectedCardIds: newSet, - panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, - }; - }), - clearCardSelection: () => set({ - selectedCardIds: new Set(), - panelAutoSelectedCardIds: new Set(), - }), + return { maximizedItemId: id }; + }), - selectMultipleCards: (ids) => set((state) => { - const newSelectedCardIds = new Set(ids); - const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); + // Workspace Split View actions + // Workspace Split View actions + toggleWorkspaceSplitView: () => set((state) => { + // Only allow enabling split view if an item is maximized + if (!state.workspaceSplitViewActive && !state.maximizedItemId) { + return { workspaceSplitViewActive: false }; + } + return { workspaceSplitViewActive: !state.workspaceSplitViewActive }; + }), + enableWorkspaceSplitView: () => set((state) => { + if (!state.maximizedItemId) return { workspaceSplitViewActive: false }; + return { workspaceSplitViewActive: true }; + }), + disableWorkspaceSplitView: () => set({ workspaceSplitViewActive: false }), + + // Legacy compatibility + setOpenModalItemId: (id) => { + set((state) => { + if (id === null) { + if (state.openPanelIds.length === 0) return {}; + return { + openPanelIds: [], + maximizedItemId: null, + panelAutoSelectedCardIds: new Set(), + }; + } else { + const isAlreadyOpen = state.openPanelIds.length === 1 && state.openPanelIds[0] === id; + if (isAlreadyOpen) return {}; + + const newSelectedCardIds = new Set(state.selectedCardIds); + const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); + + // In workspace split view, deselect the previously maximized card when opening a new one + if (state.workspaceSplitViewActive && state.maximizedItemId && state.maximizedItemId !== id) { + newSelectedCardIds.delete(state.maximizedItemId); + newPanelAutoSelectedCardIds.delete(state.maximizedItemId); + } + + // Add to selections and track as auto-selected + newSelectedCardIds.add(id); + newPanelAutoSelectedCardIds.add(id); + + return { + openPanelIds: [id], + maximizedItemId: id, + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, + }; + } + }); + }, + + setShowVersionHistory: (show) => set({ showVersionHistory: show }), + setShowCreateWorkspaceModal: (show) => set({ showCreateWorkspaceModal: show }), + setShowSheetModal: (show) => set({ showSheetModal: show }), + + // UI Preferences actions + setShowJsonView: (show) => set({ showJsonView: show }), + setSearchQuery: (query) => set({ searchQuery: query }), + + setSelectedActions: (actions) => set({ selectedActions: actions }), + clearSelectedActions: () => set({ selectedActions: [] }), + setSelectedModelId: (modelId) => set({ selectedModelId: modelId }), + + // Text selection actions + setInMultiSelectMode: (inMultiMode) => set({ inMultiSelectMode: inMultiMode }), + setInSingleSelectMode: (inSingleMode) => set({ inSingleSelectMode: inSingleMode }), + setTooltipVisible: (visible) => set({ tooltipVisible: visible }), + setSelectedHighlightColorId: (colorId) => set({ selectedHighlightColorId: colorId }), + enterMultiSelectMode: () => set({ inMultiSelectMode: true, inSingleSelectMode: false, tooltipVisible: true }), + exitMultiSelectMode: () => set({ inMultiSelectMode: false, tooltipVisible: false }), + enterSingleSelectMode: () => set({ inSingleSelectMode: true, inMultiSelectMode: false, tooltipVisible: true }), + exitSingleSelectMode: () => set({ inSingleSelectMode: false, tooltipVisible: false }), + + // Card selection actions + toggleCardSelection: (id) => set((state) => { + const newSet = new Set(state.selectedCardIds); + const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); - newPanelAutoSelectedCardIds.forEach(id => { - if (!newSelectedCardIds.has(id)) { + if (newSet.has(id)) { + newSet.delete(id); newPanelAutoSelectedCardIds.delete(id); + } else { + newSet.add(id); } - }); + return { + selectedCardIds: newSet, + panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, + }; + }), - newSelectedCardIds.forEach(id => { - newPanelAutoSelectedCardIds.delete(id); - }); + clearCardSelection: () => set({ + selectedCardIds: new Set(), + panelAutoSelectedCardIds: new Set(), + }), - return { - selectedCardIds: newSelectedCardIds, - panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, - }; - }), + selectMultipleCards: (ids) => set((state) => { + const newSelectedCardIds = new Set(ids); + const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); - setCardPlaying: (id, isPlaying) => set((state) => { - const newSet = new Set(state.playingYouTubeCardIds); - if (isPlaying) { - newSet.add(id); - } else { - newSet.delete(id); - } - return { playingYouTubeCardIds: newSet }; - }), - clearPlayingYouTubeCards: () => set({ playingYouTubeCardIds: new Set() }), + newPanelAutoSelectedCardIds.forEach(id => { + if (!newSelectedCardIds.has(id)) { + newPanelAutoSelectedCardIds.delete(id); + } + }); - // Scroll lock actions - setItemScrollLocked: (itemId, isLocked) => set((state) => { - const newMap = new Map(state.itemScrollLocked); - newMap.set(itemId, isLocked); - return { itemScrollLocked: newMap }; - }), - toggleItemScrollLocked: (itemId) => set((state) => { - const newMap = new Map(state.itemScrollLocked); - const current = newMap.get(itemId) ?? true; - newMap.set(itemId, !current); - return { itemScrollLocked: newMap }; - }), + newSelectedCardIds.forEach(id => { + newPanelAutoSelectedCardIds.delete(id); + }); - // Reply selection actions - addReplySelection: (selection) => set((state) => { - const newSelections = [...state.replySelections, selection]; - return { replySelections: newSelections }; - }), - removeReplySelection: (index) => set((state) => ({ - replySelections: state.replySelections.filter((_, i) => i !== index), - })), - clearReplySelections: () => set({ replySelections: [] }), - - // BlockNote selection actions - setBlockNoteSelection: (selection) => { - set({ blockNoteSelection: selection }); - }, - clearBlockNoteSelection: () => { - set({ blockNoteSelection: null }); - }, + return { + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, + }; + }), + setCardPlaying: (id, isPlaying) => set((state) => { + const newSet = new Set(state.playingYouTubeCardIds); + if (isPlaying) { + newSet.add(id); + } else { + newSet.delete(id); + } + return { playingYouTubeCardIds: newSet }; + }), + clearPlayingYouTubeCards: () => set({ playingYouTubeCardIds: new Set() }), + + // Scroll lock actions + setItemScrollLocked: (itemId, isLocked) => set((state) => { + const newMap = new Map(state.itemScrollLocked); + newMap.set(itemId, isLocked); + return { itemScrollLocked: newMap }; + }), + toggleItemScrollLocked: (itemId) => set((state) => { + const newMap = new Map(state.itemScrollLocked); + const current = newMap.get(itemId) ?? true; + newMap.set(itemId, !current); + return { itemScrollLocked: newMap }; + }), + + // Reply selection actions + addReplySelection: (selection) => set((state) => { + const newSelections = [...state.replySelections, selection]; + return { replySelections: newSelections }; + }), + removeReplySelection: (index) => set((state) => ({ + replySelections: state.replySelections.filter((_, i) => i !== index), + })), + clearReplySelections: () => set({ replySelections: [] }), + + // BlockNote selection actions + setBlockNoteSelection: (selection) => { + set({ blockNoteSelection: selection }); + }, + clearBlockNoteSelection: () => { + set({ blockNoteSelection: null }); + }, + + + // Utility actions + resetChatState: () => set({ + isChatExpanded: initialState.isChatExpanded, + isChatMaximized: initialState.isChatMaximized, + workspacePanelSize: initialState.workspacePanelSize, + }), + + // Chat actions + setIsChatExpanded: (expanded) => set({ isChatExpanded: expanded }), + toggleChatExpanded: () => set((state) => ({ isChatExpanded: !state.isChatExpanded })), + setIsChatMaximized: (maximized) => set({ isChatMaximized: maximized }), + toggleChatMaximized: () => set((state) => ({ isChatMaximized: !state.isChatMaximized })), + setIsThreadListVisible: (visible) => set({ isThreadListVisible: visible }), + toggleThreadListVisible: () => set((state) => ({ isThreadListVisible: !state.isThreadListVisible })), + setWorkspacePanelSize: (size) => set({ workspacePanelSize: size }), + + closeAllModals: () => set((state) => { + // Only remove auto-selected cards from selection + const newSelectedCardIds = new Set(state.selectedCardIds); + const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); - // Utility actions - resetChatState: () => set({ - isChatExpanded: initialState.isChatExpanded, - isChatMaximized: initialState.isChatMaximized, - workspacePanelSize: initialState.workspacePanelSize, - }), + state.openPanelIds.forEach(id => { + if (newPanelAutoSelectedCardIds.has(id)) { + newSelectedCardIds.delete(id); + newPanelAutoSelectedCardIds.delete(id); + } + }); - // Chat actions - setIsChatExpanded: (expanded) => set({ isChatExpanded: expanded }), - toggleChatExpanded: () => set((state) => ({ isChatExpanded: !state.isChatExpanded })), - setIsChatMaximized: (maximized) => set({ isChatMaximized: maximized }), - toggleChatMaximized: () => set((state) => ({ isChatMaximized: !state.isChatMaximized })), - setIsThreadListVisible: (visible) => set({ isThreadListVisible: visible }), - toggleThreadListVisible: () => set((state) => ({ isThreadListVisible: !state.isThreadListVisible })), - setWorkspacePanelSize: (size) => set({ workspacePanelSize: size }), - - closeAllModals: () => set((state) => { - // Only remove auto-selected cards from selection - const newSelectedCardIds = new Set(state.selectedCardIds); - const newPanelAutoSelectedCardIds = new Set(state.panelAutoSelectedCardIds); - - state.openPanelIds.forEach(id => { - if (newPanelAutoSelectedCardIds.has(id)) { - newSelectedCardIds.delete(id); - newPanelAutoSelectedCardIds.delete(id); - } - }); - - return { - openPanelIds: [], - itemPrompt: null, - maximizedItemId: null, - showVersionHistory: false, - showCreateWorkspaceModal: false, - showSheetModal: false, - selectedCardIds: newSelectedCardIds, - panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, - }; + return { + openPanelIds: [], + itemPrompt: null, + maximizedItemId: null, + showVersionHistory: false, + showCreateWorkspaceModal: false, + showSheetModal: false, + selectedCardIds: newSelectedCardIds, + panelAutoSelectedCardIds: newPanelAutoSelectedCardIds, + }; + }), }), - }), - { - name: 'thinkex-ui-preferences', - storage: createJSONStorage(() => localStorage), - partialize: (state) => ({ selectedModelId: state.selectedModelId }), - }, + { + name: 'thinkex-ui-preferences', + storage: createJSONStorage(() => localStorage), + partialize: (state) => ({ selectedModelId: state.selectedModelId }), + }, ), { name: 'UI Store' } ) @@ -550,6 +600,7 @@ export const selectPrimaryPanelId = (state: UIState) => state.openPanelIds[0] ?? export const selectSecondaryPanelId = (state: UIState) => state.openPanelIds[1] ?? null; export const selectHasSplitView = (state: UIState) => state.openPanelIds.length >= 2; export const selectIsPanelOpen = (state: UIState) => state.openPanelIds.length > 0; +export const selectIsDualPanelActive = (state: UIState) => state.openPanelIds.length === 2 && state.workspaceSplitViewActive; // Legacy compatibility selectors export const selectOpenModalItemId = (state: UIState) => state.openPanelIds[0] ?? null; From 79a412bdd04f416a429ae170abea796fe890f660 Mon Sep 17 00:00:00 2001 From: 1shCha Date: Tue, 10 Feb 2026 01:46:03 -0500 Subject: [PATCH 2/3] changes --- src/components/layout/DashboardLayout.tsx | 5 +++-- src/components/modals/ModalManager.tsx | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx index 78d33be..8ad42a4 100644 --- a/src/components/layout/DashboardLayout.tsx +++ b/src/components/layout/DashboardLayout.tsx @@ -165,9 +165,10 @@ export function DashboardLayout({ {/* DUAL-PANEL MODE: Show two item panels side-by-side */} {isDualPanel ? ( - + /* ModalManager handles the ResizablePanelGroup internally for dual mode */ +
{modalManager} - +
) : workspaceSplitViewActive && maximizedItemId ? ( /* WORKSPACE SPLIT VIEW MODE: Show workspace + item side-by-side */ diff --git a/src/components/modals/ModalManager.tsx b/src/components/modals/ModalManager.tsx index 5375ed1..c5ac5e1 100644 --- a/src/components/modals/ModalManager.tsx +++ b/src/components/modals/ModalManager.tsx @@ -4,7 +4,7 @@ import PDFViewerModal from "./PDFViewerModal"; import { VersionHistoryModal } from "@/components/workspace/VersionHistoryModal"; import type { WorkspaceEvent } from "@/lib/workspace/events"; import { useUIStore } from "@/lib/stores/ui-store"; -import { ResizablePanel, ResizableHandle } from "@/components/ui/resizable"; +import { ResizablePanel, ResizableHandle, ResizablePanelGroup } from "@/components/ui/resizable"; interface ModalManagerProps { // Card Detail Modal @@ -70,7 +70,7 @@ export function ModalManager({ <> {/* Dual-Panel Mode: Render both panels when openPanelIds.length === 2 and split view is active */} {workspaceSplitViewActive && openPanelIds.length === 2 ? ( - <> + {/* First Panel (Left) */} {openPanelIds[0] && (() => { @@ -132,7 +132,7 @@ export function ModalManager({ ); })()} - + ) : ( /* Single Panel Mode: Card Detail Modal or PDF Viewer Modal - only shown when item is maximized */ activeItemId && currentItem && maximizedItemId === currentItem.id && ( From 055f8e7bda2e28bac1e9147e60cc3b1657900bf5 Mon Sep 17 00:00:00 2001 From: 1shCha Date: Tue, 10 Feb 2026 02:04:26 -0500 Subject: [PATCH 3/3] changes --- src/lib/stores/ui-store.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/stores/ui-store.ts b/src/lib/stores/ui-store.ts index 117cab5..46c5924 100644 --- a/src/lib/stores/ui-store.ts +++ b/src/lib/stores/ui-store.ts @@ -338,6 +338,7 @@ export const useUIStore = create()( return { openPanelIds: [], maximizedItemId: null, + workspaceSplitViewActive: false, selectedCardIds: newSelectedCardIds, panelAutoSelectedCardIds: new Set(), }; @@ -391,9 +392,16 @@ export const useUIStore = create()( set((state) => { if (id === null) { if (state.openPanelIds.length === 0) return {}; + + // Correctly clear auto-selected cards + const newSelectedCardIds = new Set(state.selectedCardIds); + state.panelAutoSelectedCardIds.forEach(item => newSelectedCardIds.delete(item)); + return { openPanelIds: [], maximizedItemId: null, + workspaceSplitViewActive: false, + selectedCardIds: newSelectedCardIds, panelAutoSelectedCardIds: new Set(), }; } else {