mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 22:57:12 -04:00
feat: support preview grid/board/calendar block on web (#5401)
This commit is contained in:
parent
a0139dd475
commit
857b3aa106
49 changed files with 243 additions and 105 deletions
|
@ -27,6 +27,8 @@ export enum BlockType {
|
||||||
DividerBlock = 'divider',
|
DividerBlock = 'divider',
|
||||||
ImageBlock = 'image',
|
ImageBlock = 'image',
|
||||||
GridBlock = 'grid',
|
GridBlock = 'grid',
|
||||||
|
BoardBlock = 'board',
|
||||||
|
CalendarBlock = 'calendar',
|
||||||
OutlineBlock = 'outline',
|
OutlineBlock = 'outline',
|
||||||
TableBlock = 'table',
|
TableBlock = 'table',
|
||||||
TableCell = 'table/cell',
|
TableCell = 'table/cell',
|
||||||
|
@ -111,6 +113,10 @@ export interface TableCellBlockData extends BlockData {
|
||||||
width: number;
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DatabaseNodeData extends BlockData {
|
||||||
|
view_id: ViewId;
|
||||||
|
}
|
||||||
|
|
||||||
export enum MentionType {
|
export enum MentionType {
|
||||||
PageRef = 'page',
|
PageRef = 'page',
|
||||||
Date = 'date',
|
Date = 'date',
|
||||||
|
|
|
@ -4,5 +4,3 @@ export * from './context';
|
||||||
export * from './selector';
|
export * from './selector';
|
||||||
export * from './database.type';
|
export * from './database.type';
|
||||||
export * from './const';
|
export * from './const';
|
||||||
export * from './filter';
|
|
||||||
export * from './sort';
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ export function useFieldsSelector(visibilitys: FieldVisibility[] = defaultVisibl
|
||||||
visibility: Number(
|
visibility: Number(
|
||||||
setting?.get(YjsDatabaseKey.visibility) || FieldVisibility.AlwaysShown
|
setting?.get(YjsDatabaseKey.visibility) || FieldVisibility.AlwaysShown
|
||||||
) as FieldVisibility,
|
) as FieldVisibility,
|
||||||
wrap: setting?.get(YjsDatabaseKey.wrap),
|
wrap: setting?.get(YjsDatabaseKey.wrap) ?? true,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((column) => {
|
.filter((column) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { CollabType, YDoc, YjsEditorKey } from '@/application/collab.type';
|
import { CollabType, YDoc, YjsEditorKey } from '@/application/collab.type';
|
||||||
import { getDBName, openCollabDB } from '@/application/services/js-services/db';
|
import { getDBName, openCollabDB } from '@/application/services/js-services/db';
|
||||||
import { APIService } from '@/application/services/js-services/wasm';
|
import { APIService } from '@/application/services/js-services/wasm';
|
||||||
import { applyDocument } from '@/application/ydoc/apply';
|
import { applyYDoc } from '@/application/ydoc/apply';
|
||||||
|
|
||||||
export function fetchCollab(workspaceId: string, id: string, type: CollabType) {
|
export function fetchCollab(workspaceId: string, id: string, type: CollabType) {
|
||||||
return APIService.getCollab(workspaceId, id, type);
|
return APIService.getCollab(workspaceId, id, type);
|
||||||
|
@ -47,7 +47,7 @@ export async function getCollabStorageWithAPICall(workspaceId: string, id: strin
|
||||||
const asyncApply = async () => {
|
const asyncApply = async () => {
|
||||||
const res = await fetchCollab(workspaceId, id, type);
|
const res = await fetchCollab(workspaceId, id, type);
|
||||||
|
|
||||||
applyDocument(doc, res.state);
|
applyYDoc(doc, res.state);
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the document exists locally, apply the state asynchronously,
|
// If the document exists locally, apply the state asynchronously,
|
||||||
|
@ -95,7 +95,7 @@ export async function batchCollabs(
|
||||||
|
|
||||||
const { doc } = await getCollabStorage(id, type);
|
const { doc } = await getCollabStorage(id, type);
|
||||||
|
|
||||||
applyDocument(doc, data);
|
applyYDoc(doc, data);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { YjsEditorKey } from '@/application/collab.type';
|
import { YjsEditorKey } from '@/application/collab.type';
|
||||||
import { applyDocument } from '@/application/ydoc/apply';
|
import { applyYDoc } from '@/application/ydoc/apply';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
import * as docJson from '../../../../../cypress/fixtures/simple_doc.json';
|
import * as docJson from '../../../../../cypress/fixtures/simple_doc.json';
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ describe('apply document', () => {
|
||||||
data.set(YjsEditorKey.document, document);
|
data.set(YjsEditorKey.document, document);
|
||||||
|
|
||||||
const state = new Uint8Array(docJson.data.doc_state);
|
const state = new Uint8Array(docJson.data.doc_state);
|
||||||
applyDocument(collab, state);
|
applyYDoc(collab, state);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { CollabOrigin } from '@/application/collab.type';
|
|
||||||
import * as Y from 'yjs';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply doc state from server to client
|
|
||||||
* Note: origin is always remote
|
|
||||||
* @param doc local Y.Doc
|
|
||||||
* @param state state from server
|
|
||||||
*/
|
|
||||||
export function applyDocument(doc: Y.Doc, state: Uint8Array) {
|
|
||||||
Y.transact(
|
|
||||||
doc,
|
|
||||||
() => {
|
|
||||||
Y.applyUpdate(doc, state);
|
|
||||||
},
|
|
||||||
CollabOrigin.Remote
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1 +1,18 @@
|
||||||
export * from 'src/application/ydoc/apply/document';
|
import { CollabOrigin } from '@/application/collab.type';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply doc state from server to client
|
||||||
|
* Note: origin is always remote
|
||||||
|
* @param doc local Y.Doc
|
||||||
|
* @param state state from server
|
||||||
|
*/
|
||||||
|
export function applyYDoc(doc: Y.Doc, state: Uint8Array) {
|
||||||
|
Y.transact(
|
||||||
|
doc,
|
||||||
|
() => {
|
||||||
|
Y.applyUpdate(doc, state);
|
||||||
|
},
|
||||||
|
CollabOrigin.Remote
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { CollabType } from '@/application/collab.type';
|
|
||||||
import { useContext, createContext } from 'react';
|
import { useContext, createContext } from 'react';
|
||||||
|
|
||||||
export const IdContext = createContext<IdProviderProps | null>(null);
|
export const IdContext = createContext<IdProviderProps | null>(null);
|
||||||
|
@ -6,7 +5,6 @@ export const IdContext = createContext<IdProviderProps | null>(null);
|
||||||
interface IdProviderProps {
|
interface IdProviderProps {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
objectId: string;
|
objectId: string;
|
||||||
collabType: CollabType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IdProvider = ({ children, ...props }: IdProviderProps & { children: React.ReactNode }) => {
|
export const IdProvider = ({ children, ...props }: IdProviderProps & { children: React.ReactNode }) => {
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const RichTooltip = ({ placement = 'top', open, onClose, content, childre
|
||||||
anchorEl={childNode}
|
anchorEl={childNode}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
transition
|
transition
|
||||||
style={{ zIndex: 2000 }}
|
style={{ zIndex: 1200 }}
|
||||||
modifiers={[
|
modifiers={[
|
||||||
{
|
{
|
||||||
name: 'flip',
|
name: 'flip',
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { YDoc, YjsEditorKey } from '@/application/collab.type';
|
||||||
import { useId } from '@/components/_shared/context-provider/IdProvider';
|
import { useId } from '@/components/_shared/context-provider/IdProvider';
|
||||||
import RecordNotFound from '@/components/_shared/not-found/RecordNotFound';
|
import RecordNotFound from '@/components/_shared/not-found/RecordNotFound';
|
||||||
import { AFConfigContext } from '@/components/app/AppConfig';
|
import { AFConfigContext } from '@/components/app/AppConfig';
|
||||||
import { DatabaseHeader } from '@/components/database/components/header';
|
|
||||||
import DatabaseViews from '@/components/database/DatabaseViews';
|
import DatabaseViews from '@/components/database/DatabaseViews';
|
||||||
import { DatabaseContextProvider } from '@/components/database/DatabaseContext';
|
import { DatabaseContextProvider } from '@/components/database/DatabaseContext';
|
||||||
import { Log } from '@/utils/log';
|
import { Log } from '@/utils/log';
|
||||||
|
@ -71,19 +70,16 @@ export const Database = memo(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'relative flex h-full w-full flex-col'}>
|
<div className='appflowy-database relative flex w-full flex-1 select-text flex-col overflow-y-hidden'>
|
||||||
<DatabaseHeader viewId={objectId} />
|
<DatabaseContextProvider
|
||||||
<div className='appflowy-database relative flex w-full flex-1 select-text flex-col overflow-y-hidden'>
|
navigateToRow={navigateToRow}
|
||||||
<DatabaseContextProvider
|
viewId={viewId || objectId}
|
||||||
navigateToRow={navigateToRow}
|
doc={doc}
|
||||||
viewId={viewId || objectId}
|
rowDocMap={rows}
|
||||||
doc={doc}
|
readOnly={true}
|
||||||
rowDocMap={rows}
|
>
|
||||||
readOnly={true}
|
<DatabaseViews onChangeView={handleChangeView} currentViewId={viewId || objectId} />
|
||||||
>
|
</DatabaseContextProvider>
|
||||||
<DatabaseViews onChangeView={handleChangeView} currentViewId={viewId || objectId} />
|
|
||||||
</DatabaseContextProvider>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@ export function Calendar() {
|
||||||
const { dayPropGetter, localizer, formats, events, emptyEvents } = useCalendarSetup();
|
const { dayPropGetter, localizer, formats, events, emptyEvents } = useCalendarSetup();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'appflowy-calendar h-full max-h-[960px] min-h-[560px] px-16 pt-4 max-md:px-4'}>
|
<div className={'appflowy-calendar h-full max-h-[960px] px-16 pt-4 max-md:px-4'}>
|
||||||
<BigCalendar
|
<BigCalendar
|
||||||
components={{
|
components={{
|
||||||
toolbar: (props) => <Toolbar {...props} emptyEvents={emptyEvents} />,
|
toolbar: (props) => <Toolbar {...props} emptyEvents={emptyEvents} />,
|
||||||
|
|
|
@ -10,8 +10,9 @@ $today-highlight-bg: transparent;
|
||||||
@apply rounded-full w-[20px] h-[20px] my-1.5;
|
@apply rounded-full w-[20px] h-[20px] my-1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rbc-date-cell {
|
|
||||||
min-width: 100px;
|
.rbc-date-cell, .rbc-header {
|
||||||
|
min-width: 120px;
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ $today-highlight-bg: transparent;
|
||||||
.rbc-month-row {
|
.rbc-month-row {
|
||||||
border: 1px solid var(--line-divider);
|
border: 1px solid var(--line-divider);
|
||||||
border-top: none;
|
border-top: none;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
|
@ -57,11 +59,12 @@ $today-highlight-bg: transparent;
|
||||||
top: 0;
|
top: 0;
|
||||||
background: var(--bg-body);
|
background: var(--bg-body);
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
@apply border-b border-line-divider;
|
|
||||||
|
|
||||||
.rbc-header {
|
.rbc-header {
|
||||||
border: none;
|
border: none;
|
||||||
@apply flex items-end py-2 justify-center font-normal text-text-caption;
|
border-bottom: 1px solid var(--line-divider);
|
||||||
|
@apply flex items-end py-2 justify-center font-normal text-text-caption bg-bg-body;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,30 @@
|
||||||
import { useFieldsSelector, useNavigateToRow } from '@/application/database-yjs';
|
import { useFieldsSelector, useNavigateToRow } from '@/application/database-yjs';
|
||||||
import { Property } from '@/components/database/components/property';
|
import { Property } from '@/components/database/components/property';
|
||||||
import { IconButton } from '@mui/material';
|
import { Tooltip } from '@mui/material';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ReactComponent as ExpandMoreIcon } from '$icons/16x/full_view.svg';
|
import { ReactComponent as ExpandMoreIcon } from '$icons/16x/full_view.svg';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
function EventPaper({ rowId }: { rowId: string }) {
|
function EventPaper({ rowId }: { rowId: string }) {
|
||||||
const fields = useFieldsSelector();
|
const fields = useFieldsSelector();
|
||||||
const navigateToRow = useNavigateToRow();
|
const navigateToRow = useNavigateToRow();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'max-h-[260px] w-[360px] overflow-y-auto'}>
|
<div className={'max-h-[260px] w-[360px] overflow-y-auto'}>
|
||||||
<div className={'flex h-fit w-full flex-col items-center justify-center py-2 px-3'}>
|
<div className={'flex h-fit w-full flex-col items-center justify-center py-2 px-3'}>
|
||||||
<div className={'flex w-full items-center justify-end'}>
|
<div className={'flex w-full items-center justify-end'}>
|
||||||
<IconButton
|
<Tooltip placement={'bottom'} title={t('tooltip.openAsPage')}>
|
||||||
onClick={() => {
|
<button
|
||||||
navigateToRow?.(rowId);
|
color={'primary'}
|
||||||
}}
|
className={'rounded bg-bg-body p-1 hover:bg-fill-list-hover'}
|
||||||
size={'small'}
|
onClick={() => {
|
||||||
>
|
navigateToRow?.(rowId);
|
||||||
<ExpandMoreIcon />
|
}}
|
||||||
</IconButton>
|
>
|
||||||
|
<ExpandMoreIcon />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className={'event-properties flex w-full flex-1 flex-col gap-4 overflow-y-auto py-2'}>
|
<div className={'event-properties flex w-full flex-1 flex-col gap-4 overflow-y-auto py-2'}>
|
||||||
{fields.map((field) => {
|
{fields.map((field) => {
|
||||||
|
|
|
@ -3,26 +3,45 @@ import { useNavigateToRow, useRowMetaSelector } from '@/application/database-yjs
|
||||||
import { TextCell as CellType, CellProps } from '@/components/database/components/cell/cell.type';
|
import { TextCell as CellType, CellProps } from '@/components/database/components/cell/cell.type';
|
||||||
import { TextCell } from '@/components/database/components/cell/text';
|
import { TextCell } from '@/components/database/components/cell/text';
|
||||||
import { Tooltip } from '@mui/material';
|
import { Tooltip } from '@mui/material';
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export function PrimaryCell(props: CellProps<CellType>) {
|
export function PrimaryCell(props: CellProps<CellType>) {
|
||||||
const navigateToRow = useNavigateToRow();
|
const navigateToRow = useNavigateToRow();
|
||||||
const { rowId } = props;
|
const { rowId } = props;
|
||||||
// const icon = null;
|
|
||||||
const icon = useRowMetaSelector(rowId)?.icon;
|
const icon = useRowMetaSelector(rowId)?.icon;
|
||||||
|
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const table = document.querySelector('.grid-table');
|
||||||
|
|
||||||
|
if (!table) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseMove = (e: Event) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
|
||||||
|
if (target.closest('.grid-row-cell')?.getAttribute('data-row-id') === rowId) {
|
||||||
|
setHover(true);
|
||||||
|
} else {
|
||||||
|
setHover(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
table.addEventListener('mousemove', onMouseMove);
|
||||||
|
return () => {
|
||||||
|
table.removeEventListener('mousemove', onMouseMove);
|
||||||
|
};
|
||||||
|
}, [rowId]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={'primary-cell relative flex min-h-full w-full items-center gap-2'}>
|
||||||
onMouseEnter={() => setHover(true)}
|
|
||||||
onMouseLeave={() => setHover(false)}
|
|
||||||
className={'primary-cell relative flex w-full items-center gap-2'}
|
|
||||||
>
|
|
||||||
{icon && <div className={'h-4 w-4'}>{icon}</div>}
|
{icon && <div className={'h-4 w-4'}>{icon}</div>}
|
||||||
<TextCell {...props} />
|
<div className={'flex-1 overflow-x-hidden'}>
|
||||||
|
<TextCell {...props} />
|
||||||
|
</div>
|
||||||
|
|
||||||
{hover && (
|
{hover && (
|
||||||
<Tooltip placement={'bottom'} title={t('tooltip.openAsPage')}>
|
<Tooltip placement={'bottom'} title={t('tooltip.openAsPage')}>
|
||||||
|
|
|
@ -35,12 +35,12 @@ function RelationItems({ style, cell, fieldId }: { cell: RelationCell; fieldId:
|
||||||
}, [workspaceId, databaseId, databaseService, rowIds]);
|
}, [workspaceId, databaseId, databaseService, rowIds]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} className={'flex items-center gap-2'}>
|
<div style={style} className={'relation-cell flex w-full items-center gap-2'}>
|
||||||
{rowIds.map((rowId) => {
|
{rowIds.map((rowId) => {
|
||||||
const rowDoc = rows?.get(rowId);
|
const rowDoc = rows?.get(rowId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={rowId} className={'cursor-pointer underline'}>
|
<div key={rowId} className={'w-full cursor-pointer underline'}>
|
||||||
{rowDoc && databasePrimaryFieldId && (
|
{rowDoc && databasePrimaryFieldId && (
|
||||||
<RelationPrimaryValue rowDoc={rowDoc} fieldId={databasePrimaryFieldId} />
|
<RelationPrimaryValue rowDoc={rowDoc} fieldId={databasePrimaryFieldId} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -31,7 +31,10 @@ export function SelectOptionCell({ cell, fieldId, style, placeholder }: CellProp
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} className={'flex h-full w-full cursor-pointer items-center gap-1 overflow-x-hidden'}>
|
<div
|
||||||
|
style={style}
|
||||||
|
className={'select-option-cell flex h-full w-full cursor-pointer items-center gap-1 overflow-x-hidden'}
|
||||||
|
>
|
||||||
{renderSelectedOptions(selectOptionIds)}
|
{renderSelectedOptions(selectOptionIds)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ export function TextCell({ cell, style }: CellProps<TextCellType>) {
|
||||||
|
|
||||||
if (!cell?.data) return null;
|
if (!cell?.data) return null;
|
||||||
return (
|
return (
|
||||||
<div style={style} className={`cursor-text leading-[1.2] ${readOnly ? 'select-text' : ''}`}>
|
<div style={style} className={`text-cell w-full cursor-text leading-[1.2] ${readOnly ? 'select-text' : ''}`}>
|
||||||
{cell?.data}
|
{cell?.data}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -94,10 +94,10 @@ export const GridTable = ({ scrollLeft, columnWidth, columns, onScrollLeft }: Gr
|
||||||
const row = data.rows[rowIndex];
|
const row = data.rows[rowIndex];
|
||||||
const column = data.columns[columnIndex] as RenderColumn;
|
const column = data.columns[columnIndex] as RenderColumn;
|
||||||
|
|
||||||
const classList = ['flex', 'items-center', 'overflow-hidden'];
|
const classList = ['flex', 'items-center', 'overflow-hidden', 'grid-row-cell'];
|
||||||
|
|
||||||
if (column.wrap) {
|
if (column.wrap) {
|
||||||
classList.push('whitespace-pre-wrap', 'break-words');
|
classList.push('wrap-cell');
|
||||||
} else {
|
} else {
|
||||||
classList.push('whitespace-nowrap');
|
classList.push('whitespace-nowrap');
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,7 @@ export const GridTable = ({ scrollLeft, columnWidth, columns, onScrollLeft }: Gr
|
||||||
if (row.type === RenderRowType.Row) {
|
if (row.type === RenderRowType.Row) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
data-row-id={row.rowId}
|
||||||
className={classList.join(' ')}
|
className={classList.join(' ')}
|
||||||
style={{ ...style, borderLeftWidth: columnIndex === 1 || column.type === GridColumnType.Action ? 0 : 1 }}
|
style={{ ...style, borderLeftWidth: columnIndex === 1 || column.type === GridColumnType.Action ? 0 : 1 }}
|
||||||
>
|
>
|
||||||
|
@ -153,6 +154,7 @@ export const GridTable = ({ scrollLeft, columnWidth, columns, onScrollLeft }: Gr
|
||||||
columnCount={columns.length}
|
columnCount={columns.length}
|
||||||
columnWidth={(index) => columnWidth(index, width)}
|
columnWidth={(index) => columnWidth(index, width)}
|
||||||
rowHeight={rowHeight}
|
rowHeight={rowHeight}
|
||||||
|
className={'grid-table'}
|
||||||
overscanRowCount={5}
|
overscanRowCount={5}
|
||||||
overscanColumnCount={5}
|
overscanColumnCount={5}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -83,7 +83,7 @@ export const DatabaseTabs = forwardRef<HTMLDivElement, DatabaseTabBarProps>(
|
||||||
icon={<Icon className={'h-4 w-4'} />}
|
icon={<Icon className={'h-4 w-4'} />}
|
||||||
iconPosition='start'
|
iconPosition='start'
|
||||||
color='inherit'
|
color='inherit'
|
||||||
label={name || t('grid.title.placeholder')}
|
label={<span className={'max-w-[120px] truncate'}>{name || t('grid.title.placeholder')}</span>}
|
||||||
value={viewId}
|
value={viewId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
|
|
||||||
export const Database = lazy(() => import('./Database'));
|
export const Database = lazy(() => import('./Database'));
|
||||||
export const DatabaseRow = lazy(() => import('./DatabaseRow'));
|
|
||||||
|
|
|
@ -51,3 +51,5 @@ export const Document = () => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default Document;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { YDoc } from '@/application/collab.type';
|
import { YDoc } from '@/application/collab.type';
|
||||||
import { DocumentTest } from '@/../cypress/support/document';
|
import { DocumentTest } from '@/../cypress/support/document';
|
||||||
import { applyDocument } from '@/application/ydoc/apply';
|
import { applyYDoc } from '@/application/ydoc/apply';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
import { Editor } from './Editor';
|
import { Editor } from './Editor';
|
||||||
|
@ -20,7 +20,7 @@ describe('<Editor />', () => {
|
||||||
const doc = new Y.Doc();
|
const doc = new Y.Doc();
|
||||||
const state = new Uint8Array(docJson.data.doc_state);
|
const state = new Uint8Array(docJson.data.doc_state);
|
||||||
|
|
||||||
applyDocument(doc, state);
|
applyYDoc(doc, state);
|
||||||
renderEditor(doc);
|
renderEditor(doc);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function BoardBlock() {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BoardBlock;
|
|
@ -0,0 +1,7 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function CalendarBlock() {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CalendarBlock;
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { IdProvider, useId } from '@/components/_shared/context-provider/IdProvider';
|
||||||
|
import { Database } from '@/components/database';
|
||||||
|
import { DatabaseNode, EditorElementProps } from '@/components/editor/editor.type';
|
||||||
|
import React, { forwardRef, memo, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { BlockType } from '@/application/collab.type';
|
||||||
|
|
||||||
|
export const DatabaseBlock = memo(
|
||||||
|
forwardRef<HTMLDivElement, EditorElementProps<DatabaseNode>>(({ node, children, ...attributes }, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const viewId = node.data.view_id;
|
||||||
|
const workspaceId = useId()?.workspaceId;
|
||||||
|
const type = node.type;
|
||||||
|
|
||||||
|
const style = useMemo(() => {
|
||||||
|
const style = {};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case BlockType.GridBlock:
|
||||||
|
Object.assign(style, {
|
||||||
|
height: 360,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case BlockType.CalendarBlock:
|
||||||
|
case BlockType.BoardBlock:
|
||||||
|
Object.assign(style, {
|
||||||
|
height: 560,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}, [type]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div {...attributes} className={`relative w-full cursor-pointer py-2`}>
|
||||||
|
<div ref={ref} className={'absolute left-0 top-0 h-full w-full caret-transparent'}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<div contentEditable={false} style={style} className={`container-bg flex w-full flex-col px-3`}>
|
||||||
|
{viewId ? (
|
||||||
|
<IdProvider workspaceId={workspaceId} objectId={viewId}>
|
||||||
|
<Database />
|
||||||
|
</IdProvider>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={'mt-[10%] flex h-full w-full flex-col items-center gap-2 px-16 text-text-caption max-md:px-4'}
|
||||||
|
>
|
||||||
|
<div className={'text-sm font-medium'}>{t('document.plugins.database.noDataSource')}</div>
|
||||||
|
<div className={'text-xs'}>{t('grid.relation.noDatabaseSelected')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default DatabaseBlock;
|
|
@ -0,0 +1,7 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function GridBlock() {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridBlock;
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './DatabaseBlock';
|
|
@ -1,12 +1,12 @@
|
||||||
import { BlockType } from '@/application/collab.type';
|
import { BlockType } from '@/application/collab.type';
|
||||||
import { BulletedListIcon } from '@/components/editor/components/blocks/bulleted_list';
|
import { BulletedListIcon } from '@/components/editor/components/blocks/bulleted-list';
|
||||||
import { NumberListIcon } from '@/components/editor/components/blocks/numbered_list';
|
import { NumberListIcon } from '@/components/editor/components/blocks/numbered-list';
|
||||||
import ToggleIcon from '@/components/editor/components/blocks/toggle_list/ToggleIcon';
|
import ToggleIcon from '@/components/editor/components/blocks/toggle-list/ToggleIcon';
|
||||||
import { TextNode } from '@/components/editor/editor.type';
|
import { TextNode } from '@/components/editor/editor.type';
|
||||||
import React, { FC, useCallback, useMemo } from 'react';
|
import React, { FC, useCallback, useMemo } from 'react';
|
||||||
import { ReactEditor, useSlate } from 'slate-react';
|
import { ReactEditor, useSlate } from 'slate-react';
|
||||||
import { Editor, Element } from 'slate';
|
import { Editor, Element } from 'slate';
|
||||||
import CheckboxIcon from '../todo_list/CheckboxIcon';
|
import CheckboxIcon from '@/components/editor/components/blocks/todo-list/CheckboxIcon';
|
||||||
|
|
||||||
export function useStartIcon(node: TextNode) {
|
export function useStartIcon(node: TextNode) {
|
||||||
const editor = useSlate();
|
const editor = useSlate();
|
||||||
|
@ -37,7 +37,7 @@ export function useStartIcon(node: TextNode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Component className={`text-block-icon relative`} block={block} />;
|
return <Component className={`text-block-icon relative h-[24px] w-[24px]`} block={block} />;
|
||||||
}, [Component, block]);
|
}, [Component, block]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { BlockData, BlockType, InlineBlockType, YjsEditorKey } from '@/application/collab.type';
|
import { BlockData, BlockType, InlineBlockType, YjsEditorKey } from '@/application/collab.type';
|
||||||
import { BulletedList } from '@/components/editor/components/blocks/bulleted_list';
|
import { BulletedList } from '@/components/editor/components/blocks/bulleted-list';
|
||||||
import { Callout } from '@/components/editor/components/blocks/callout';
|
import { Callout } from '@/components/editor/components/blocks/callout';
|
||||||
import { CodeBlock } from '@/components/editor/components/blocks/code';
|
import { CodeBlock } from '@/components/editor/components/blocks/code';
|
||||||
import { DividerNode } from '@/components/editor/components/blocks/divider';
|
import { DividerNode } from '@/components/editor/components/blocks/divider';
|
||||||
import { Heading } from '@/components/editor/components/blocks/heading';
|
import { Heading } from '@/components/editor/components/blocks/heading';
|
||||||
import { ImageBlock } from '@/components/editor/components/blocks/image';
|
import { ImageBlock } from '@/components/editor/components/blocks/image';
|
||||||
import { MathEquation } from '@/components/editor/components/blocks/math_equation';
|
import { MathEquation } from '@/components/editor/components/blocks/math-equation';
|
||||||
import { NumberedList } from '@/components/editor/components/blocks/numbered_list';
|
import { NumberedList } from '@/components/editor/components/blocks/numbered-list';
|
||||||
import { Outline } from '@/components/editor/components/blocks/outline';
|
import { Outline } from '@/components/editor/components/blocks/outline';
|
||||||
import { Page } from '@/components/editor/components/blocks/page';
|
import { Page } from '@/components/editor/components/blocks/page';
|
||||||
import { Paragraph } from '@/components/editor/components/blocks/paragraph';
|
import { Paragraph } from '@/components/editor/components/blocks/paragraph';
|
||||||
import { Quote } from '@/components/editor/components/blocks/quote';
|
import { Quote } from '@/components/editor/components/blocks/quote';
|
||||||
import { TableBlock, TableCellBlock } from '@/components/editor/components/blocks/table';
|
import { TableBlock, TableCellBlock } from '@/components/editor/components/blocks/table';
|
||||||
import { Text } from '@/components/editor/components/blocks/text';
|
import { Text } from '@/components/editor/components/blocks/text';
|
||||||
import { TodoList } from '@/components/editor/components/blocks/todo_list';
|
import { TodoList } from 'src/components/editor/components/blocks/todo-list';
|
||||||
import { ToggleList } from '@/components/editor/components/blocks/toggle_list';
|
import { ToggleList } from 'src/components/editor/components/blocks/toggle-list';
|
||||||
import { UnSupportedBlock } from '@/components/editor/components/element/UnSupportedBlock';
|
import { UnSupportedBlock } from '@/components/editor/components/element/UnSupportedBlock';
|
||||||
import { Formula } from '@/components/editor/components/leaf/formula';
|
import { Formula } from '@/components/editor/components/leaf/formula';
|
||||||
import { Mention } from '@/components/editor/components/leaf/mention';
|
import { Mention } from '@/components/editor/components/leaf/mention';
|
||||||
|
@ -22,6 +22,7 @@ import { EditorElementProps, TextNode } from '@/components/editor/editor.type';
|
||||||
import { renderColor } from '@/utils/color';
|
import { renderColor } from '@/utils/color';
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
import { RenderElementProps } from 'slate-react';
|
import { RenderElementProps } from 'slate-react';
|
||||||
|
import { DatabaseBlock } from 'src/components/editor/components/blocks/database';
|
||||||
|
|
||||||
export const Element = ({
|
export const Element = ({
|
||||||
element: node,
|
element: node,
|
||||||
|
@ -64,6 +65,10 @@ export const Element = ({
|
||||||
return TableBlock;
|
return TableBlock;
|
||||||
case BlockType.TableCell:
|
case BlockType.TableCell:
|
||||||
return TableCellBlock;
|
return TableCellBlock;
|
||||||
|
case BlockType.GridBlock:
|
||||||
|
case BlockType.BoardBlock:
|
||||||
|
case BlockType.CalendarBlock:
|
||||||
|
return DatabaseBlock;
|
||||||
default:
|
default:
|
||||||
return UnSupportedBlock;
|
return UnSupportedBlock;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,7 @@ export function Leaf({ attributes, children, leaf }: RenderLeafProps) {
|
||||||
const classList = [leaf.prism_token, leaf.prism_token && 'token', leaf.class_name].filter(Boolean);
|
const classList = [leaf.prism_token, leaf.prism_token && 'token', leaf.class_name].filter(Boolean);
|
||||||
|
|
||||||
if (leaf.code) {
|
if (leaf.code) {
|
||||||
newChildren = (
|
newChildren = <span className={'bg-line-divider font-medium text-[#EB5757]'}>{newChildren}</span>;
|
||||||
<span className={'bg-fill-list-active bg-opacity-50 text-xs font-medium text-[#EB5757]'}>{newChildren}</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leaf.underline) {
|
if (leaf.underline) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
TableCellBlockData,
|
TableCellBlockData,
|
||||||
BlockId,
|
BlockId,
|
||||||
BlockData,
|
BlockData,
|
||||||
|
DatabaseNodeData,
|
||||||
} from '@/application/collab.type';
|
} from '@/application/collab.type';
|
||||||
import { HTMLAttributes } from 'react';
|
import { HTMLAttributes } from 'react';
|
||||||
import { Element } from 'slate';
|
import { Element } from 'slate';
|
||||||
|
@ -120,6 +121,12 @@ export interface TableCellNode extends BlockNode {
|
||||||
data: TableCellBlockData;
|
data: TableCellBlockData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DatabaseNode extends BlockNode {
|
||||||
|
type: BlockType.GridBlock | BlockType.BoardBlock | BlockType.CalendarBlock;
|
||||||
|
blockId: string;
|
||||||
|
data: DatabaseNodeData;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EditorElementProps<T = Element> extends HTMLAttributes<HTMLDivElement> {
|
export interface EditorElementProps<T = Element> extends HTMLAttributes<HTMLDivElement> {
|
||||||
node: T;
|
node: T;
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,8 +91,22 @@
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
boxShadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
backgroundColor: var(--bg-body);
|
background-color: var(--bg-body);
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-row-cell.wrap-cell {
|
||||||
|
.text-cell {
|
||||||
|
@apply py-2 break-words whitespace-pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relation-cell {
|
||||||
|
@apply py-2 break-words whitespace-pre-wrap flex-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-option-cell {
|
||||||
|
@apply flex-wrap py-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import { Database, DatabaseRow } from '@/components/database';
|
import { useId } from '@/components/_shared/context-provider/IdProvider';
|
||||||
|
import { DatabaseHeader } from '@/components/database/components/header';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import DatabaseRow from '@/components/database/DatabaseRow';
|
||||||
|
import Database from '@/components/database/Database';
|
||||||
|
|
||||||
function DatabasePage() {
|
function DatabasePage() {
|
||||||
|
const objectId = useId()?.objectId;
|
||||||
const [search] = useSearchParams();
|
const [search] = useSearchParams();
|
||||||
const rowId = search.get('r');
|
const rowId = search.get('r');
|
||||||
|
|
||||||
|
@ -10,7 +14,12 @@ function DatabasePage() {
|
||||||
return <DatabaseRow rowId={rowId} />;
|
return <DatabaseRow rowId={rowId} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Database />;
|
return (
|
||||||
|
<div className={'relative flex h-full w-full flex-col'}>
|
||||||
|
<DatabaseHeader viewId={objectId} />
|
||||||
|
<Database />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DatabasePage;
|
export default DatabasePage;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { CollabType } from '@/application/collab.type';
|
|
||||||
import { IdProvider } from '@/components/_shared/context-provider/IdProvider';
|
import { IdProvider } from '@/components/_shared/context-provider/IdProvider';
|
||||||
import DatabasePage from '@/pages/DatabasePage';
|
import React, { lazy, useMemo } from 'react';
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import DocumentPage from '@/pages/DocumentPage';
|
import DocumentPage from '@/pages/DocumentPage';
|
||||||
|
|
||||||
|
const DatabasePage = lazy(() => import('./DatabasePage'));
|
||||||
|
|
||||||
enum URL_COLLAB_TYPE {
|
enum URL_COLLAB_TYPE {
|
||||||
DOCUMENT = 'document',
|
DOCUMENT = 'document',
|
||||||
GRID = 'grid',
|
GRID = 'grid',
|
||||||
|
@ -12,13 +12,6 @@ enum URL_COLLAB_TYPE {
|
||||||
CALENDAR = 'calendar',
|
CALENDAR = 'calendar',
|
||||||
}
|
}
|
||||||
|
|
||||||
const collabTypeMap: Record<string, CollabType> = {
|
|
||||||
[URL_COLLAB_TYPE.DOCUMENT]: CollabType.Document,
|
|
||||||
[URL_COLLAB_TYPE.GRID]: CollabType.WorkspaceDatabase,
|
|
||||||
[URL_COLLAB_TYPE.BOARD]: CollabType.WorkspaceDatabase,
|
|
||||||
[URL_COLLAB_TYPE.CALENDAR]: CollabType.WorkspaceDatabase,
|
|
||||||
};
|
|
||||||
|
|
||||||
function ProductPage() {
|
function ProductPage() {
|
||||||
const { workspaceId, type, objectId } = useParams();
|
const { workspaceId, type, objectId } = useParams();
|
||||||
const PageComponent = useMemo(() => {
|
const PageComponent = useMemo(() => {
|
||||||
|
@ -38,7 +31,7 @@ function ProductPage() {
|
||||||
if (!workspaceId || !type || !objectId) return null;
|
if (!workspaceId || !type || !objectId) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IdProvider workspaceId={workspaceId} objectId={objectId} collabType={collabTypeMap[type]}>
|
<IdProvider workspaceId={workspaceId} objectId={objectId}>
|
||||||
{PageComponent && <PageComponent />}
|
{PageComponent && <PageComponent />}
|
||||||
</IdProvider>
|
</IdProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -72,7 +72,7 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
envPrefix: ['AF', 'TAURI_'],
|
envPrefix: ['AF', 'TAURI_'],
|
||||||
esbuild: {
|
esbuild: {
|
||||||
drop: ['console', 'debugger'],
|
drop: isDev ? [] : ['console', 'debugger'],
|
||||||
},
|
},
|
||||||
build: !!process.env.TAURI_PLATFORM
|
build: !!process.env.TAURI_PLATFORM
|
||||||
? {
|
? {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue