mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
chore: fix open container
This commit is contained in:
@@ -3,9 +3,11 @@ import { View, ViewLayout } from '../types';
|
||||
import {
|
||||
isDatabaseLayout,
|
||||
isDatabaseContainer,
|
||||
isEmbeddedView,
|
||||
getDatabaseIdFromExtra,
|
||||
isReferencedDatabaseView,
|
||||
getFirstChildView,
|
||||
getDatabaseTabViewIds,
|
||||
} from '../view-utils';
|
||||
|
||||
/**
|
||||
@@ -472,6 +474,116 @@ describe('view-utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDatabaseTabViewIds', () => {
|
||||
it('filters embedded views from container tabs', () => {
|
||||
const gridView = createMockView({
|
||||
view_id: 'grid-view',
|
||||
layout: ViewLayout.Grid,
|
||||
extra: { is_space: false, database_id: 'db-1' },
|
||||
});
|
||||
const embeddedBoardView = createMockView({
|
||||
view_id: 'board-view',
|
||||
layout: ViewLayout.Board,
|
||||
extra: { is_space: false, database_id: 'db-1', embedded: true },
|
||||
});
|
||||
const container = createMockView({
|
||||
view_id: 'container',
|
||||
layout: ViewLayout.Grid,
|
||||
extra: {
|
||||
is_space: false,
|
||||
is_database_container: true,
|
||||
database_id: 'db-1',
|
||||
},
|
||||
children: [gridView, embeddedBoardView],
|
||||
});
|
||||
|
||||
expect(isEmbeddedView(gridView)).toBe(false);
|
||||
expect(isEmbeddedView(embeddedBoardView)).toBe(true);
|
||||
expect(getDatabaseTabViewIds(gridView.view_id, container)).toEqual([gridView.view_id]);
|
||||
});
|
||||
|
||||
it('shows only the embedded view when opening it from the sidebar', () => {
|
||||
const gridView = createMockView({
|
||||
view_id: 'grid-view',
|
||||
layout: ViewLayout.Grid,
|
||||
extra: { is_space: false, database_id: 'db-1' },
|
||||
});
|
||||
const embeddedBoardView = createMockView({
|
||||
view_id: 'board-view',
|
||||
layout: ViewLayout.Board,
|
||||
extra: { is_space: false, database_id: 'db-1', embedded: true },
|
||||
});
|
||||
const container = createMockView({
|
||||
view_id: 'container',
|
||||
layout: ViewLayout.Grid,
|
||||
extra: {
|
||||
is_space: false,
|
||||
is_database_container: true,
|
||||
database_id: 'db-1',
|
||||
},
|
||||
children: [gridView, embeddedBoardView],
|
||||
});
|
||||
|
||||
expect(getDatabaseTabViewIds(embeddedBoardView.view_id, container)).toEqual([
|
||||
embeddedBoardView.view_id,
|
||||
]);
|
||||
});
|
||||
|
||||
it('falls back to display tabs when opening a container directly', () => {
|
||||
const gridView = createMockView({
|
||||
view_id: 'grid-view',
|
||||
layout: ViewLayout.Grid,
|
||||
extra: { is_space: false, database_id: 'db-1' },
|
||||
});
|
||||
const embeddedBoardView = createMockView({
|
||||
view_id: 'board-view',
|
||||
layout: ViewLayout.Board,
|
||||
extra: { is_space: false, database_id: 'db-1', embedded: true },
|
||||
});
|
||||
const container = createMockView({
|
||||
view_id: 'container',
|
||||
layout: ViewLayout.Grid,
|
||||
extra: {
|
||||
is_space: false,
|
||||
is_database_container: true,
|
||||
database_id: 'db-1',
|
||||
},
|
||||
children: [gridView, embeddedBoardView],
|
||||
});
|
||||
|
||||
expect(getDatabaseTabViewIds(container.view_id, container)).toEqual([gridView.view_id]);
|
||||
});
|
||||
|
||||
it('keeps all tabs when a database only has embedded views', () => {
|
||||
const embeddedGridView = createMockView({
|
||||
view_id: 'embedded-grid',
|
||||
layout: ViewLayout.Grid,
|
||||
extra: { is_space: false, database_id: 'db-1', embedded: true },
|
||||
});
|
||||
const embeddedBoardView = createMockView({
|
||||
view_id: 'embedded-board',
|
||||
layout: ViewLayout.Board,
|
||||
extra: { is_space: false, database_id: 'db-1', embedded: true },
|
||||
});
|
||||
const container = createMockView({
|
||||
view_id: 'container',
|
||||
layout: ViewLayout.Grid,
|
||||
extra: {
|
||||
is_space: false,
|
||||
is_database_container: true,
|
||||
database_id: 'db-1',
|
||||
embedded: true,
|
||||
},
|
||||
children: [embeddedGridView, embeddedBoardView],
|
||||
});
|
||||
|
||||
expect(getDatabaseTabViewIds(embeddedGridView.view_id, container)).toEqual([
|
||||
embeddedGridView.view_id,
|
||||
embeddedBoardView.view_id,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Backward Compatibility: Views without container support
|
||||
*
|
||||
|
||||
@@ -924,6 +924,10 @@ export interface ViewExtra {
|
||||
};
|
||||
is_hidden_space?: boolean;
|
||||
|
||||
// Whether this view is embedded inside a document (e.g. a linked database view).
|
||||
// This is aligned with Desktop/Flutter and server-side `EXTRA_KEY_EMBEDDED`.
|
||||
embedded?: boolean;
|
||||
|
||||
// Database container support (aligned with Desktop/Flutter)
|
||||
// Reference: AppFlowy-Premium/frontend/doc/context/database_container_behavior.md
|
||||
is_database_container?: boolean; // True if this view is a database container
|
||||
|
||||
@@ -25,6 +25,16 @@ export function isDatabaseLayout(layout: ViewLayout): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a view is marked as embedded in its extra.
|
||||
*
|
||||
* Embedded views are created inside documents (e.g. database blocks) and should not
|
||||
* appear as tabs in the "source" database container page.
|
||||
*/
|
||||
export function isEmbeddedView(view: View | null | undefined): boolean {
|
||||
return view?.extra?.embedded === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if view is a database container.
|
||||
*
|
||||
@@ -91,3 +101,40 @@ export function getFirstChildView(view: View | null | undefined): View | undefin
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of database view IDs that should be displayed in the tab bar.
|
||||
*
|
||||
* Mirrors Desktop/Flutter behavior:
|
||||
* - Database containers can have both non-embedded "display views" and embedded views.
|
||||
* - Embedded views should not appear as tabs when viewing the source database container.
|
||||
* - When navigating directly to an embedded child view from the sidebar, show only that view.
|
||||
*/
|
||||
export function getDatabaseTabViewIds(currentViewId: string, containerView: View): string[] {
|
||||
const children = containerView.children ?? [];
|
||||
const childViewIds = children.map((child) => child.view_id);
|
||||
|
||||
if (childViewIds.length === 0) {
|
||||
return [currentViewId];
|
||||
}
|
||||
|
||||
const nonEmbeddedChildIds = children
|
||||
.filter((child) => !isEmbeddedView(child))
|
||||
.map((child) => child.view_id);
|
||||
|
||||
const displayViewIds = nonEmbeddedChildIds.length > 0 ? nonEmbeddedChildIds : childViewIds;
|
||||
|
||||
// If the current view is one of the display views, show the full display list.
|
||||
if (displayViewIds.includes(currentViewId)) {
|
||||
return displayViewIds;
|
||||
}
|
||||
|
||||
// If the current view is a child but not a display view, treat it as an embedded
|
||||
// view opened as a standalone page and only show itself as a single tab.
|
||||
if (childViewIds.includes(currentViewId)) {
|
||||
return [currentViewId];
|
||||
}
|
||||
|
||||
// Otherwise, treat it as opening the container (or a stale route param).
|
||||
return displayViewIds;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Suspense, useCallback, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { ViewComponentProps, ViewLayout, YDatabase, YjsEditorKey } from '@/application/types';
|
||||
import { isDatabaseContainer } from '@/application/view-utils';
|
||||
import { getDatabaseTabViewIds, isDatabaseContainer } from '@/application/view-utils';
|
||||
import { findView } from '@/components/_shared/outline/utils';
|
||||
import ComponentLoading from '@/components/_shared/progress/ComponentLoading';
|
||||
import CalendarSkeleton from '@/components/_shared/skeleton/CalendarSkeleton';
|
||||
@@ -23,7 +23,7 @@ function DatabaseView(props: ViewComponentProps) {
|
||||
* The database's page ID in the folder/outline structure.
|
||||
* This is the main entry point for the database and remains constant.
|
||||
*/
|
||||
const databasePageId = viewMeta.viewId;
|
||||
const databasePageId = viewMeta.viewId || '';
|
||||
|
||||
const view = useMemo(() => {
|
||||
if (!outline || !databasePageId) return;
|
||||
@@ -53,12 +53,12 @@ function DatabaseView(props: ViewComponentProps) {
|
||||
|
||||
const visibleViewIds = useMemo(() => {
|
||||
if (containerView) {
|
||||
return containerView.children?.map((v) => v.view_id) || [];
|
||||
return getDatabaseTabViewIds(databasePageId, containerView);
|
||||
}
|
||||
|
||||
if (!view) return [];
|
||||
return [view.view_id, ...(view.children?.map((v) => v.view_id) || [])];
|
||||
}, [containerView, view]);
|
||||
}, [containerView, view, databasePageId]);
|
||||
|
||||
const pageMeta = useMemo(() => {
|
||||
if (!pageView) {
|
||||
|
||||
Reference in New Issue
Block a user