fix: displaying linked datbaase view

This commit is contained in:
Nathan
2025-11-17 14:52:41 +08:00
parent b54128c601
commit da7f2a9929
8 changed files with 88 additions and 42 deletions

View File

@@ -7,6 +7,7 @@
"dev": "vite",
"dev:coverage": "cross-env COVERAGE=true vite",
"build": "vite build",
"clean": "rm -rf dist storybook-static coverage .nyc_output node_modules/.vite node_modules/.cache",
"type-check": "tsc --noEmit --project tsconfig.web.json",
"type-check:watch": "tsc --build --watch",
"lint": "pnpm type-check && eslint --quiet --ext .js,.ts,.tsx . --ignore-path .eslintignore.web",
@@ -283,4 +284,4 @@
"pnpm": "^10.9.0"
},
"packageManager": "pnpm@10.9.0+sha512.0486e394640d3c1fb3c9d43d49cf92879ff74f8516959c235308f5a8f62e2e19528a65cdc2a3058f587cde71eba3d5b56327c8c33a97e4c4051ca48a10ca2d5f"
}
}

View File

@@ -65,18 +65,47 @@ function Database(props: Database2Props) {
loadView,
navigateToView,
modalRowId,
isDocumentBlock,
} = props;
const database = doc.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase | null;
const view = database?.get(YjsDatabaseKey.views)?.get(iidIndex);
const database = doc.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase | null;
const views = database?.get(YjsDatabaseKey.views);
// Find view by iid field (views map uses numeric keys like "0", "1", "2")
const findViewByIid = useCallback((viewsMap: typeof views, targetIid: string) => {
if (!viewsMap) return null;
// Try direct access first (for standalone databases)
const directView = viewsMap.get(targetIid);
if (directView) return directView;
// Search by iid field (for embedded databases)
const viewsData = viewsMap.toJSON();
const keys = Object.keys(viewsData);
for (const key of keys) {
const v = viewsMap.get(key);
const viewIid = v?.get?.(YjsDatabaseKey.iid);
if (viewIid === targetIid) {
return v;
}
}
return null;
}, []);
const view = findViewByIid(views, iidIndex);
const rowOrders = view?.get(YjsDatabaseKey.row_orders);
const [rowIds, setRowIds] = useState<RowId[]>([]);
const [rowDocMap, setRowDocMap] = useState<Record<RowId, YDoc> | null>(null);
const updateRowMap = useCallback(async () => {
const newRowMap: Record<RowId, YDoc> = {};
if (!rowIds || !createRowDoc) return;
if (!rowIds || !createRowDoc) {
return;
}
const promises = rowIds.map(async (id) => {
if (!id) {
@@ -108,10 +137,6 @@ function Database(props: Database2Props) {
void debounceUpdateRowMap();
}, [debounceUpdateRowMap]);
useEffect(() => {
console.debug('Database.tsx: database', database?.toJSON());
console.debug('Database.tsx: rowDocMap', rowDocMap);
}, [rowDocMap, database]);
const createNewRowDoc = useCallback(
async (rowKey: string) => {
@@ -127,7 +152,10 @@ function Database(props: Database2Props) {
);
const handleUpdateRowDocMap = useCallback(async () => {
setRowIds(rowOrders?.toJSON().map(({ id }: { id: string }) => id) || []);
const rowOrdersData = rowOrders?.toJSON() || [];
const ids = rowOrdersData.map(({ id }: { id: string }) => id);
setRowIds(ids);
}, [rowOrders]);
useEffect(() => {

View File

@@ -38,8 +38,7 @@ export const Column = memo(
}, [rows, readOnly]);
const { columnRef, headerRef, state, isDragging } = useColumnHeaderDrag(id);
const [scrollerContainer, setScrollerContainer] = React.useState<HTMLDivElement | null>(null);
const { contextValue, columnInnerRef } = useCardsDrag(id, rows, scrollerContainer);
const { contextValue, columnInnerRef } = useCardsDrag(id, rows);
const getCards = useCallback(
(_columnId: string): Row[] => {
@@ -76,8 +75,6 @@ export const Column = memo(
columnId={id}
data={data}
fieldId={fieldId}
setScrollElement={setScrollerContainer}
/>
</div>
{state.type === StateType.IS_COLUMN_OVER && state.closestEdge && (

View File

@@ -1,8 +1,6 @@
import { Row, useReadOnly } from '@/application/database-yjs';
import { useBoardDragContext } from '@/components/database/components/board/drag-and-drop/board-context';
import { ColumnDragContextProps } from '@/components/database/components/board/drag-and-drop/column-context';
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -23,41 +21,45 @@ type State = { type: StateType.IDLE } | { type: StateType.IS_CARD_OVER };
const idle: State = { type: StateType.IDLE };
const isCardOver: State = { type: StateType.IS_CARD_OVER };
export function useCardsDrag(columnId: string, rows: Row[], scrollerContainer: HTMLDivElement | null) {
export function useCardsDrag(columnId: string, rows: Row[]) {
const { instanceId, registerColumn } = useBoardDragContext();
const columnInnerRef = useRef<HTMLDivElement | null>(null);
const [state, setState] = useState<State>(idle);
const stableItems = useRef(rows);
const readOnly = useReadOnly();
const cleanupRef = useRef<(() => void) | null>(null);
useEffect(() => {
stableItems.current = rows;
}, [rows]);
useEffect(() => {
if (!columnInnerRef.current || !scrollerContainer || readOnly) {
if (!columnInnerRef.current || readOnly) {
return;
}
return combine(
dropTargetForElements({
element: columnInnerRef.current,
getData: () => ({ columnId }),
canDrop: ({ source }) => {
return source.data.instanceId === instanceId && source.data.type === 'card';
},
getIsSticky: () => true,
onDragEnter: () => setState(isCardOver),
onDragLeave: () => setState(idle),
onDragStart: () => setState(isCardOver),
onDrop: () => setState(idle),
}),
autoScrollForElements({
element: scrollerContainer,
canScroll: ({ source }) => source.data.instanceId === instanceId && source.data.type === 'card',
})
);
}, [readOnly, columnId, instanceId, registerColumn, scrollerContainer]);
cleanupRef.current?.();
const teardown = dropTargetForElements({
element: columnInnerRef.current,
getData: () => ({ columnId }),
canDrop: ({ source }) => {
return source.data.instanceId === instanceId && source.data.type === 'card';
},
getIsSticky: () => true,
onDragEnter: () => setState(isCardOver),
onDragLeave: () => setState(idle),
onDragStart: () => setState(isCardOver),
onDrop: () => setState(idle),
});
cleanupRef.current = teardown;
return () => {
teardown();
cleanupRef.current = null;
};
}, [readOnly, columnId, instanceId, registerColumn]);
const getCardIndex = useCallback((rowId: string) => {
return stableItems.current.findIndex((item) => item.id === rowId);

View File

@@ -13,6 +13,7 @@ import DatabaseStickyBottomOverlay from '@/components/database/components/sticky
import DatabaseStickyHorizontalScrollbar from '@/components/database/components/sticky-overlay/DatabaseStickyHorizontalScrollbar';
import DatabaseStickyTopOverlay from '@/components/database/components/sticky-overlay/DatabaseStickyTopOverlay';
import { getScrollParent } from '@/components/global-comment/utils';
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
import { useNavigationKey } from './useNavigationKey';
@@ -88,6 +89,18 @@ export const Group = ({ groupId }: GroupProps) => {
const innerRef = useRef<HTMLDivElement | null>(null);
const stickyHeaderRef = useRef<HTMLDivElement>(null);
// Auto-scroll for card dragging (registered once at Group level)
useEffect(() => {
if (!verticalScrollContainer || readOnly) return;
const cleanup = autoScrollForElements({
element: verticalScrollContainer,
canScroll: ({ source }) => source.data.instanceId === contextValue.instanceId && source.data.type === 'card',
});
return cleanup;
}, [verticalScrollContainer, readOnly, contextValue.instanceId]);
useEffect(() => {
const inner = innerRef.current;
const columnsEl = ref.current;

View File

@@ -14,6 +14,7 @@ import { useResizePositioning } from './hooks/useResizePositioning';
export const DatabaseBlock = memo(
forwardRef<HTMLDivElement, EditorElementProps<DatabaseNode>>(({ node, children, ...attributes }, ref) => {
const viewId = node.data.view_id;
const parentId = node.data.parent_id;
const context = useEditorContext();
const workspaceId = context.workspaceId;
const navigateToView = context?.navigateToView;
@@ -25,8 +26,12 @@ export const DatabaseBlock = memo(
const editor = useSlateStatic();
const readOnly = useReadOnly() || editor.isElementReadOnly(node as unknown as Element);
// For linked databases, load the parent view (original database)
// For inline databases, parent_id === current document, so load view_id
const viewToLoad = parentId || viewId;
const { notFound, doc, selectedViewId, visibleViewIds, iidName, onChangeView, loadViewMeta } = useDatabaseLoading({
viewId,
viewId: viewToLoad,
loadView,
loadViewMeta: context?.loadViewMeta,
});

View File

@@ -63,14 +63,14 @@ export const DatabaseContent = ({
workspaceId={workspaceId}
doc={doc}
iidIndex={viewId}
viewId={selectedViewId}
viewId={viewId}
createRowDoc={createRowDoc}
loadView={loadView}
navigateToView={navigateToView}
onOpenRowPage={onOpenRowPage}
loadViewMeta={loadViewMeta}
iidName={iidName}
visibleViewIds={visibleViewIds}
visibleViewIds={[viewId]}
onChangeView={onChangeView}
showActions={true}
paddingStart={paddingStart}

View File

@@ -25,9 +25,9 @@ import {
} from '@/application/types';
// import { ReactComponent as AIWriterIcon } from '@/assets/slash_menu_icon_ai_writer.svg';
import { ReactComponent as EmojiIcon } from '@/assets/icons/add_emoji.svg';
import { ReactComponent as GridIcon } from '@/assets/slash_menu_icon_grid.svg';
import { ReactComponent as BoardIcon } from '@/assets/slash_menu_icon_kanban.svg';
import { ReactComponent as CalendarIcon } from '@/assets/slash_menu_icon_calendar.svg';
import { ReactComponent as GridIcon } from '@/assets/icons/grid.svg';
import { ReactComponent as BoardIcon } from '@/assets/icons/board.svg';
import { ReactComponent as CalendarIcon } from '@/assets/icons/calendar.svg';
import { ReactComponent as AskAIIcon } from '@/assets/icons/ai.svg';
import { ReactComponent as BulletedListIcon } from '@/assets/icons/bulleted_list.svg';
import { ReactComponent as CalloutIcon } from '@/assets/icons/callout.svg';