chore: fix switch view (#71)

* chore: fix switch view

* chore: fix the issue of switch database view
This commit is contained in:
Kilu.He
2025-09-12 10:06:43 +08:00
committed by GitHub
parent 02ac0ee3ee
commit 3e94fe7cde
10 changed files with 79 additions and 106 deletions

View File

@@ -73,15 +73,25 @@ function Database(props: Database2Props) {
const newRowMap: Record<RowId, YDoc> = {}; const newRowMap: Record<RowId, YDoc> = {};
if (!rowIds || !createRowDoc) return; if (!rowIds || !createRowDoc) return;
for (const id of rowIds) {
const promises = rowIds.map(async (id) => {
if (!id) { if (!id) {
continue; return;
} }
const rowKey = `${doc.guid}_rows_${id}`; const rowKey = `${doc.guid}_rows_${id}`;
const rowDoc = await createRowDoc(rowKey);
newRowMap[id] = await createRowDoc(rowKey); return { id, rowDoc };
});
const results = await Promise.all(promises);
results.forEach((result) => {
if (result) {
newRowMap[result.id] = result.rowDoc;
} }
});
setRowDocMap(newRowMap); setRowDocMap(newRowMap);
}, [createRowDoc, doc.guid, rowIds]); }, [createRowDoc, doc.guid, rowIds]);

View File

@@ -1,4 +1,3 @@
import { AnimatePresence, motion } from 'framer-motion';
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react'; import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
@@ -13,6 +12,7 @@ import { DatabaseTabs } from '@/components/database/components/tabs';
import { Calendar } from '@/components/database/fullcalendar'; import { Calendar } from '@/components/database/fullcalendar';
import { Grid } from '@/components/database/grid'; import { Grid } from '@/components/database/grid';
import { ElementFallbackRender } from '@/components/error/ElementFallbackRender'; import { ElementFallbackRender } from '@/components/error/ElementFallbackRender';
import { Progress } from '@/components/ui/progress';
import DatabaseConditions from 'src/components/database/components/conditions/DatabaseConditions'; import DatabaseConditions from 'src/components/database/components/conditions/DatabaseConditions';
@@ -31,6 +31,7 @@ function DatabaseViews({
}) { }) {
const { childViews, viewIds } = useDatabaseViewsSelector(iidIndex, visibleViewIds); const { childViews, viewIds } = useDatabaseViewsSelector(iidIndex, visibleViewIds);
const [isLoading, setIsLoading] = useState(false);
const [layout, setLayout] = useState<DatabaseViewLayout | null>(null); const [layout, setLayout] = useState<DatabaseViewLayout | null>(null);
const value = useMemo(() => { const value = useMemo(() => {
return Math.max( return Math.max(
@@ -54,6 +55,7 @@ function DatabaseViews({
const observerEvent = () => { const observerEvent = () => {
setLayout(Number(activeView.get(YjsDatabaseKey.layout)) as DatabaseViewLayout); setLayout(Number(activeView.get(YjsDatabaseKey.layout)) as DatabaseViewLayout);
setIsLoading(false);
}; };
observerEvent(); observerEvent();
@@ -65,61 +67,13 @@ function DatabaseViews({
}; };
}, [activeView]); }, [activeView]);
const view = useMemo(() => { const handleViewChange = useCallback(
// 使用 viewId 和 layout 的组合作为 key确保在任一变化时都有动画 (newViewId: string) => {
const animationKey = `${layout}-${viewId}`; setIsLoading(true);
onChangeView(newViewId);
switch (layout) { },
case DatabaseViewLayout.Grid: [onChangeView]
return (
<motion.div
key={animationKey}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
className="h-full w-full"
>
<Grid />
</motion.div>
); );
case DatabaseViewLayout.Board:
return (
<motion.div
key={animationKey}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
className="h-full w-full"
>
<Board />
</motion.div>
);
case DatabaseViewLayout.Calendar:
return (
<motion.div
key={animationKey}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
className="h-full w-full"
>
<Calendar />
</motion.div>
);
}
}, [layout, viewId]);
const skeleton = useMemo(() => { const skeleton = useMemo(() => {
switch (layout) { switch (layout) {
@@ -134,6 +88,17 @@ function DatabaseViews({
} }
}, [layout]); }, [layout]);
const view = useMemo(() => {
switch (layout) {
case DatabaseViewLayout.Grid:
return <Grid />;
case DatabaseViewLayout.Board:
return <Board />;
case DatabaseViewLayout.Calendar:
return <Calendar />;
}
}, [layout]);
return ( return (
<> <>
<DatabaseConditionsContext.Provider <DatabaseConditionsContext.Provider
@@ -148,19 +113,20 @@ function DatabaseViews({
viewName={viewName} viewName={viewName}
iidIndex={iidIndex} iidIndex={iidIndex}
selectedViewId={viewId} selectedViewId={viewId}
setSelectedViewId={onChangeView} setSelectedViewId={handleViewChange}
viewIds={viewIds} viewIds={viewIds}
/> />
<DatabaseConditions /> <DatabaseConditions />
<div className={'flex h-full w-full flex-1 flex-col overflow-hidden'}> <div className={'relative flex h-full w-full flex-1 flex-col overflow-hidden'}>
<Suspense fallback={skeleton}> <Suspense fallback={skeleton}>
<ErrorBoundary fallbackRender={ElementFallbackRender}> <ErrorBoundary fallbackRender={ElementFallbackRender}>{view}</ErrorBoundary>
<AnimatePresence mode="wait">
{view}
</AnimatePresence>
</ErrorBoundary>
</Suspense> </Suspense>
{isLoading && (
<div className='absolute inset-0 z-50 flex items-center justify-center bg-white/50 backdrop-blur-sm'>
<Progress />
</div>
)}
</div> </div>
</DatabaseConditionsContext.Provider> </DatabaseConditionsContext.Provider>
</> </>

View File

@@ -18,7 +18,7 @@ import { NoDateButton } from './NoDateButton';
import { CalendarViewType } from './types'; import { CalendarViewType } from './types';
interface CustomToolbarProps { interface CustomToolbarProps {
calendar: CalendarApi | null; calendar?: CalendarApi | null;
onViewChange?: (view: CalendarViewType) => void; onViewChange?: (view: CalendarViewType) => void;
slideDirection?: 'up' | 'down' | null; slideDirection?: 'up' | 'down' | null;
emptyEvents?: CalendarEvent[]; emptyEvents?: CalendarEvent[];

View File

@@ -103,30 +103,26 @@ function Calendar() {
return ( return (
<div className='calendar-wrapper pb-5'> <div className='calendar-wrapper pb-5'>
{/* Normal toolbar - always visible */} {/* Normal toolbar - always visible */}
{calendarData && (
<div ref={normalToolbarRef}> <div ref={normalToolbarRef}>
<StickyCalendarToolbar <StickyCalendarToolbar
calendar={calendarData.calendarApi} calendar={calendarData?.calendarApi}
currentView={calendarData.currentView} currentView={calendarData?.currentView}
onViewChange={calendarData.handleViewChange} onViewChange={calendarData?.handleViewChange}
slideDirection={slideDirection} slideDirection={slideDirection}
emptyEvents={calendarData.emptyEvents} emptyEvents={calendarData?.emptyEvents}
onDragStart={handleDragStart} onDragStart={handleDragStart}
draggingRowId={draggingRowId} draggingRowId={draggingRowId}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
/> />
</div> </div>
)}
{/* Normal week header - always visible for comparison */} {/* Normal week header - always visible for comparison */}
{calendarData && calendarData.shouldShowWeekHeader && (
<StickyWeekHeader <StickyWeekHeader
headerCells={calendarData.weekHeaderCells} headerCells={calendarData?.weekHeaderCells}
visible={true} visible={true}
scrollLeft={calendarData.weekHeaderScrollLeft} scrollLeft={calendarData?.weekHeaderScrollLeft}
currentView={calendarData.currentView} currentView={calendarData?.currentView}
/> />
)}
{/* Calendar content without toolbar */} {/* Calendar content without toolbar */}
<CalendarContent <CalendarContent

View File

@@ -10,9 +10,9 @@ import { CalendarViewType } from './types';
* Props for StickyCalendarToolbar component * Props for StickyCalendarToolbar component
*/ */
interface StickyCalendarToolbarProps { interface StickyCalendarToolbarProps {
calendar: CalendarApi | null; calendar?: CalendarApi | null;
currentView: CalendarViewType; currentView?: CalendarViewType;
onViewChange: (view: CalendarViewType) => void; onViewChange?: (view: CalendarViewType) => void;
slideDirection?: 'up' | 'down' | null; slideDirection?: 'up' | 'down' | null;
emptyEvents?: CalendarEvent[]; emptyEvents?: CalendarEvent[];
onDragStart?: (rowId: string) => void; onDragStart?: (rowId: string) => void;

View File

@@ -19,7 +19,7 @@ interface HeaderCellData {
* Props for StickyWeekHeader component * Props for StickyWeekHeader component
*/ */
interface StickyWeekHeaderProps { interface StickyWeekHeaderProps {
headerCells: HeaderCellData[]; headerCells?: HeaderCellData[];
visible: boolean; visible: boolean;
scrollLeft?: number; scrollLeft?: number;
currentView?: CalendarViewType; currentView?: CalendarViewType;
@@ -59,7 +59,7 @@ export function StickyWeekHeader({
return currentView === CalendarViewType.TIME_GRID_WEEK; return currentView === CalendarViewType.TIME_GRID_WEEK;
}, [currentView]); }, [currentView]);
if (!visible || headerCells.length === 0) { if (!visible || headerCells?.length === 0) {
return null; return null;
} }
@@ -110,7 +110,7 @@ export function StickyWeekHeader({
)} )}
{/* Date columns */} {/* Date columns */}
{headerCells.map((cell, index) => ( {headerCells?.map((cell, index) => (
<th <th
key={index} key={index}
className={`fc-col-header-cell ${cell.isToday ? 'fc-day-today' : ''} ${ className={`fc-col-header-cell ${cell.isToday ? 'fc-day-today' : ''} ${

View File

@@ -6,7 +6,7 @@ import { createHotkey, HOT_KEY_NAME, isInputElement } from '@/utils/hotkeys';
import { CalendarViewType } from '../types'; import { CalendarViewType } from '../types';
interface UseCalendarKeyboardShortcutsProps { interface UseCalendarKeyboardShortcutsProps {
calendar: CalendarApi | null; calendar?: CalendarApi | null;
currentView: CalendarViewType; currentView: CalendarViewType;
onViewChange?: (view: CalendarViewType) => void; onViewChange?: (view: CalendarViewType) => void;
onPrev?: () => void; onPrev?: () => void;

View File

@@ -1,8 +1,9 @@
import { useEffect } from 'react';
import { useDatabaseContext, useDatabaseViewId } from '@/application/database-yjs'; import { useDatabaseContext, useDatabaseViewId } from '@/application/database-yjs';
import { useRenderFields } from '@/components/database/components/grid/grid-column'; import { useRenderFields } from '@/components/database/components/grid/grid-column';
import GridVirtualizer from '@/components/database/components/grid/grid-table/GridVirtualizer'; import GridVirtualizer from '@/components/database/components/grid/grid-table/GridVirtualizer';
import { GridProvider } from '@/components/database/grid/GridProvider'; import { GridProvider } from '@/components/database/grid/GridProvider';
import { useEffect } from 'react';
export function Grid() { export function Grid() {
const { fields } = useRenderFields(); const { fields } = useRenderFields();
@@ -18,13 +19,13 @@ export function Grid () {
return ( return (
<GridProvider> <GridProvider>
<div data-testid="database-grid" className={`database-grid relative grid-table-${viewId} flex w-full flex-1 flex-col`}> <div
<GridVirtualizer data-testid='database-grid'
columns={fields} className={`database-grid relative grid-table-${viewId} flex w-full flex-1 flex-col`}
/> >
<GridVirtualizer columns={fields} />
</div> </div>
</GridProvider> </GridProvider>
); );
} }

View File

@@ -185,7 +185,7 @@ export const useSync = (ws: AppflowyWebSocketType, bc: BroadcastChannelType, eve
return existingContext; return existingContext;
} }
console.log(`Registering sync context for objectId ${context.doc.guid} with collabType ${context.collabType}`); console.debug(`Registering sync context for objectId ${context.doc.guid} with collabType ${context.collabType}`);
context.emit = (message) => { context.emit = (message) => {
sendMessage(message); sendMessage(message);
postMessage(message); postMessage(message);