mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
fix: displaying linked datbaase view
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user