feat: support open row page (#5400)

This commit is contained in:
Kilu.He 2024-05-23 16:35:45 +08:00 committed by GitHub
parent 9a5dbbb3ce
commit a0139dd475
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 658 additions and 213 deletions

View File

@ -1,5 +1,7 @@
import { YDatabaseRow, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
import { RowMetaKey } from '@/application/database-yjs/database.type';
import * as Y from 'yjs';
import { v5 as uuidv5, parse as uuidParse } from 'uuid';
export const DEFAULT_ROW_HEIGHT = 37;
export const MIN_COLUMN_WIDTH = 100;
@ -14,3 +16,9 @@ export const getCell = (rowId: string, fieldId: string, rowMetas: Y.Map<YDoc>) =
export const getCellData = (rowId: string, fieldId: string, rowMetas: Y.Map<YDoc>) => {
return getCell(rowId, fieldId, rowMetas)?.get(YjsDatabaseKey.data);
};
export const metaIdFromRowId = (rowId: string) => {
const namespace = uuidParse(rowId);
return (key: RowMetaKey) => uuidv5(key, namespace).toString();
};

View File

@ -8,6 +8,7 @@ export interface DatabaseContextState {
doc: YDoc;
viewId: string;
rowDocMap: Y.Map<YDoc>;
navigateToRow?: (rowId: string) => void;
}
export const DatabaseContext = createContext<DatabaseContextState | null>(null);
@ -20,12 +21,18 @@ export const useDatabase = () => {
return database;
};
export const useRowMeta = (rowId: string) => {
const rows = useContext(DatabaseContext)?.rowDocMap;
const rowMetaDoc = rows?.get(rowId);
const rowMeta = rowMetaDoc?.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database_row) as YDatabaseRow;
export const useNavigateToRow = () => {
return useContext(DatabaseContext)?.navigateToRow;
};
return rowMeta;
export const useRow = (rowId: string) => {
const rows = useContext(DatabaseContext)?.rowDocMap;
return rows?.get(rowId)?.getMap(YjsEditorKey.data_section);
};
export const useRowData = (rowId: string) => {
return useRow(rowId)?.get(YjsEditorKey.database_row) as YDatabaseRow;
};
export const useViewId = () => {

View File

@ -63,3 +63,10 @@ export interface CalendarLayoutSetting {
showWeekends: boolean;
layout: CalendarLayout;
}
export enum RowMetaKey {
DocumentId = 'document_id',
IconId = 'icon_id',
CoverId = 'cover_id',
IsDocumentEmpty = 'is_document_empty',
}

View File

@ -1,11 +1,12 @@
import { FieldId, SortId, YDatabaseField, YjsDatabaseKey, YjsFolderKey } from '@/application/collab.type';
import { getCell, MIN_COLUMN_WIDTH } from '@/application/database-yjs/const';
import { FieldId, SortId, YDatabaseField, YjsDatabaseKey, YjsEditorKey, YjsFolderKey } from '@/application/collab.type';
import { getCell, metaIdFromRowId, MIN_COLUMN_WIDTH } from '@/application/database-yjs/const';
import {
DatabaseContext,
useDatabase,
useDatabaseFields,
useDatabaseView,
useRowMeta,
useRow,
useRowData,
useRows,
useViewId,
} from '@/application/database-yjs/context';
@ -18,8 +19,8 @@ import { parseYDatabaseCellToCell } from '@/components/database/components/cell/
import { DateTimeCell } from '@/components/database/components/cell/cell.type';
import dayjs from 'dayjs';
import debounce from 'lodash-es/debounce';
import { useContext, useEffect, useMemo, useState } from 'react';
import { CalendarLayoutSetting, FieldType, FieldVisibility, Filter, SortCondition } from './database.type';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { CalendarLayoutSetting, FieldType, FieldVisibility, Filter, RowMetaKey, SortCondition } from './database.type';
export interface Column {
fieldId: string;
@ -473,7 +474,7 @@ export function useRowOrdersSelector() {
}
export function useCellSelector({ rowId, fieldId }: { rowId: string; fieldId: string }) {
const row = useRowMeta(rowId);
const row = useRowData(rowId);
const cell = row?.get(YjsDatabaseKey.cells)?.get(fieldId);
const [cellValue, setCellValue] = useState(() => (cell ? parseYDatabaseCellToCell(cell) : undefined));
@ -585,3 +586,59 @@ export function useCalendarLayoutSetting() {
return setting;
}
export function usePrimaryFieldId() {
const database = useDatabase();
const [primaryFieldId, setPrimaryFieldId] = React.useState<string | null>(null);
useEffect(() => {
const fields = database?.get(YjsDatabaseKey.fields);
const primaryFieldId = Array.from(fields?.keys() || []).find((fieldId) => {
return fields?.get(fieldId)?.get(YjsDatabaseKey.is_primary);
});
setPrimaryFieldId(primaryFieldId || null);
}, [database]);
return primaryFieldId;
}
export interface RowMeta {
documentId: string;
cover: string;
icon: string;
isEmptyDocument: boolean;
}
export const useRowMetaSelector = (rowId: string) => {
const [meta, setMeta] = useState<RowMeta | null>();
const yMeta = useRow(rowId)?.get(YjsEditorKey.meta);
useEffect(() => {
if (!yMeta) return;
const onChange = () => {
const metaJson = yMeta.toJSON();
const getData = metaIdFromRowId(rowId);
const icon = metaJson[getData(RowMetaKey.IconId)];
const cover = metaJson[getData(RowMetaKey.CoverId)];
const documentId = getData(RowMetaKey.DocumentId);
const isEmptyDocument = metaJson[getData(RowMetaKey.IsDocumentEmpty)];
return setMeta({
icon,
cover,
documentId,
isEmptyDocument,
});
};
onChange();
yMeta.observe(onChange);
return () => {
yMeta.unobserve(onChange);
};
}, [rowId, yMeta]);
return meta;
};

View File

@ -62,6 +62,7 @@ export function parseCellDataForSort(field: YDatabaseField, data: string | boole
switch (fieldType) {
case FieldType.RichText:
case FieldType.URL:
return data ? data : '\uFFFF';
case FieldType.Number:
return data;
case FieldType.Checkbox:

View File

@ -17,7 +17,8 @@ export class JSDatabaseService implements DatabaseService {
async getDatabase(
workspaceId: string,
databaseId: string
databaseId: string,
rowIds?: string[]
): Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;
@ -36,25 +37,24 @@ export class JSDatabaseService implements DatabaseService {
const database = databaseDoc.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase;
const viewId = database.get(YjsDatabaseKey.metas)?.get(YjsDatabaseKey.iid)?.toString();
const rowOrders = database.get(YjsDatabaseKey.views)?.get(viewId)?.get(YjsDatabaseKey.row_orders);
const rowIds = rowOrders.toJSON() as {
const rowOrdersIds = rowOrders.toJSON() as {
id: string;
}[];
if (!rowIds) {
if (!rowOrdersIds) {
throw new Error('Database rows not found');
}
if (isLoaded) {
for (const row of rowIds) {
const { doc } = await getCollabStorage(row.id, CollabType.DatabaseRow);
const ids = rowIds ? rowIds : rowOrdersIds.map((item) => item.id);
rowsFolder.set(row.id, doc);
if (isLoaded) {
for (const id of ids) {
const { doc } = await getCollabStorage(id, CollabType.DatabaseRow);
rowsFolder.set(id, doc);
}
} else {
const rows = await this.loadDatabaseRows(
workspaceId,
rowIds.map((item) => item.id)
);
const rows = await this.loadDatabaseRows(workspaceId, ids);
rows.forEach((row, id) => {
rowsFolder.set(id, row);
@ -63,6 +63,27 @@ export class JSDatabaseService implements DatabaseService {
this.loadedDatabaseId.add(databaseId);
if (!rowIds) {
// Update rows if new rows are added
rowOrders?.observe((event) => {
if (event.changes.added.size > 0) {
const rowIds = rowOrders.toJSON() as {
id: string;
}[];
console.log('Update rows', rowIds);
void this.loadDatabaseRows(
workspaceId,
rowIds.map((item) => item.id)
).then((newRows) => {
newRows.forEach((row, id) => {
rowsFolder.set(id, row);
});
});
}
});
}
return {
databaseDoc,
rows: rowsFolder as Y.Map<YDoc>,
@ -71,7 +92,8 @@ export class JSDatabaseService implements DatabaseService {
async openDatabase(
workspaceId: string,
viewId: string
viewId: string,
rowIds?: string[]
): Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;
@ -112,28 +134,8 @@ export class JSDatabaseService implements DatabaseService {
throw new Error('Database not found');
}
const { databaseDoc, rows } = await this.getDatabase(workspaceId, databaseMeta.database_id);
const database = databaseDoc.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase;
const rowOrders = database.get(YjsDatabaseKey.views)?.get(viewId)?.get(YjsDatabaseKey.row_orders);
const { databaseDoc, rows } = await this.getDatabase(workspaceId, databaseMeta.database_id, rowIds);
// Update rows if new rows are added
rowOrders?.observe((event) => {
if (event.changes.added.size > 0) {
const rowIds = rowOrders.toJSON() as {
id: string;
}[];
console.log('Update rows', rowIds);
void this.loadDatabaseRows(
workspaceId,
rowIds.map((item) => item.id)
).then((newRows) => {
newRows.forEach((row, id) => {
rows.set(id, row);
});
});
}
});
const handleUpdate = (update: Uint8Array, origin: CollabOrigin) => {
if (origin === CollabOrigin.LocalSync) {
// Send the update to the server

View File

@ -37,14 +37,16 @@ export interface DocumentService {
export interface DatabaseService {
openDatabase: (
workspaceId: string,
viewId: string
viewId: string,
rowIds?: string[]
) => Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;
}>;
getDatabase: (
workspaceId: string,
databaseId: string
databaseId: string,
rowIds?: string[]
) => Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;

View File

@ -57,15 +57,36 @@ export const YjsEditor = {
export function withYjs<T extends Editor>(
editor: T,
doc: Y.Doc,
localOrigin: CollabOrigin = CollabOrigin.Local
{
localOrigin,
includeRoot = true,
}: {
localOrigin: CollabOrigin;
includeRoot?: boolean;
}
): T & YjsEditor {
const e = editor as T & YjsEditor;
const { apply, onChange } = e;
e.sharedRoot = doc.getMap(YjsEditorKey.data_section) as YSharedRoot;
const initializeDocumentContent = () => {
const content = yDocToSlateContent(doc, includeRoot);
if (!content) {
return;
}
e.children = content.children;
Editor.normalize(editor, { force: true });
};
e.applyRemoteEvents = (events: Array<YEvent<YSharedRoot>>, _: Transaction) => {
YjsEditor.flushLocalChanges(e);
// TODO: handle remote events
// This is a temporary implementation to apply remote events to slate
initializeDocumentContent();
Editor.withoutNormalizing(editor, () => {
events.forEach((event) => {
translateYjsEvent(e.sharedRoot, editor, event).forEach((op) => {
@ -87,17 +108,8 @@ export function withYjs<T extends Editor>(
throw new Error('Already connected');
}
const content = yDocToSlateContent(doc, true);
if (!content) {
return;
}
console.log(content);
initializeDocumentContent();
e.sharedRoot.observeDeep(handleYEvents);
e.children = content.children;
Editor.normalize(editor, { force: true });
connectSet.add(e);
};

View File

@ -3,10 +3,9 @@ import * as Y from 'yjs';
import { Editor, Operation } from 'slate';
export function translateYArrayEvent(
sharedRoot: YSharedRoot,
editor: Editor,
event: Y.YEvent<Y.Array<string>>
_sharedRoot: YSharedRoot,
_editor: Editor,
_event: Y.YEvent<Y.Array<string>>
): Operation[] {
console.log('translateYArrayEvent', sharedRoot, editor, event);
return [];
}

View File

@ -13,7 +13,6 @@ import { translateYTextEvent } from 'src/application/slate-yjs/utils/translateYj
* @param op
*/
export function translateYjsEvent(sharedRoot: YSharedRoot, editor: Editor, event: Y.YEvent<YSharedRoot>): Operation[] {
console.log('translateYjsEvent', event);
if (event instanceof Y.YMapEvent) {
return translateYMapEvent(sharedRoot, editor, event);
}

View File

@ -3,10 +3,9 @@ import * as Y from 'yjs';
import { Editor, Operation } from 'slate';
export function translateYMapEvent(
sharedRoot: YSharedRoot,
editor: Editor,
event: Y.YEvent<Y.Map<unknown>>
_sharedRoot: YSharedRoot,
_editor: Editor,
_event: Y.YEvent<Y.Map<unknown>>
): Operation[] {
console.log('translateYMapEvent', sharedRoot, editor, event);
return [];
}

View File

@ -2,7 +2,6 @@ import { YSharedRoot } from '@/application/collab.type';
import * as Y from 'yjs';
import { Editor, Operation } from 'slate';
export function translateYTextEvent(sharedRoot: YSharedRoot, editor: Editor, event: Y.YEvent<Y.Text>): Operation[] {
console.log('translateYTextEvent', sharedRoot, editor, event);
export function translateYTextEvent(_sharedRoot: YSharedRoot, _editor: Editor, _event: Y.YEvent<Y.Text>): Operation[] {
return [];
}

View File

@ -2,9 +2,9 @@ import { YDoc, YjsEditorKey } from '@/application/collab.type';
import { useId } from '@/components/_shared/context-provider/IdProvider';
import RecordNotFound from '@/components/_shared/not-found/RecordNotFound';
import { AFConfigContext } from '@/components/app/AppConfig';
import { DatabaseHeader } from '@/components/database/components/header';
import DatabaseViews from '@/components/database/DatabaseViews';
import { DatabaseContextProvider } from '@/components/database/DatabaseContext';
import DatabaseTitle from '@/components/database/DatabaseTitle';
import { Log } from '@/utils/log';
import CircularProgress from '@mui/material/CircularProgress';
import React, { memo, useCallback, useContext, useEffect, useState } from 'react';
@ -14,13 +14,14 @@ import * as Y from 'yjs';
export const Database = memo(() => {
const { objectId, workspaceId } = useId() || {};
const [search, setSearch] = useSearchParams();
const viewId = search.get('v');
const [doc, setDoc] = useState<YDoc | null>(null);
const [rows, setRows] = useState<Y.Map<YDoc> | null>(null); // Map<rowId, YDoc
const [notFound, setNotFound] = useState<boolean>(false);
const databaseService = useContext(AFConfigContext)?.service?.databaseService;
const handleOpenDocument = useCallback(async () => {
const handleOpenDatabase = useCallback(async () => {
if (!databaseService || !workspaceId || !objectId) return;
try {
@ -28,6 +29,8 @@ export const Database = memo(() => {
const { databaseDoc, rows } = await databaseService.openDatabase(workspaceId, objectId);
console.log('databaseDoc', databaseDoc.getMap(YjsEditorKey.data_section).toJSON());
console.log('rows', rows);
setDoc(databaseDoc);
setRows(rows);
} catch (e) {
@ -38,8 +41,8 @@ export const Database = memo(() => {
useEffect(() => {
setNotFound(false);
void handleOpenDocument();
}, [handleOpenDocument]);
void handleOpenDatabase();
}, [handleOpenDatabase]);
const handleChangeView = useCallback(
(viewId: string) => {
@ -48,13 +51,18 @@ export const Database = memo(() => {
[setSearch]
);
if (!objectId) return null;
const navigateToRow = useCallback(
(rowId: string) => {
setSearch({ r: rowId });
},
[setSearch]
);
if (!doc) {
if (notFound || !objectId) {
return <RecordNotFound open={notFound} workspaceId={workspaceId} />;
}
if (!rows) {
if (!rows || !doc) {
return (
<div className={'flex h-full w-full items-center justify-center'}>
<CircularProgress />
@ -64,9 +72,15 @@ export const Database = memo(() => {
return (
<div className={'relative flex h-full w-full flex-col'}>
<DatabaseTitle viewId={objectId} />
<DatabaseHeader viewId={objectId} />
<div className='appflowy-database relative flex w-full flex-1 select-text flex-col overflow-y-hidden'>
<DatabaseContextProvider viewId={viewId || objectId} doc={doc} rowDocMap={rows} readOnly={true}>
<DatabaseContextProvider
navigateToRow={navigateToRow}
viewId={viewId || objectId}
doc={doc}
rowDocMap={rows}
readOnly={true}
>
<DatabaseViews onChangeView={handleChangeView} currentViewId={viewId || objectId} />
</DatabaseContextProvider>
</div>

View File

@ -0,0 +1,79 @@
import { YDoc, YjsEditorKey } from '@/application/collab.type';
import { useId } from '@/components/_shared/context-provider/IdProvider';
import { AFConfigContext } from '@/components/app/AppConfig';
import { DatabaseRowProperties, DatabaseRowSubDocument } from '@/components/database/components/database-row';
import DatabaseRowHeader from '@/components/database/components/header/DatabaseRowHeader';
import { DatabaseContextProvider } from '@/components/database/DatabaseContext';
import { Log } from '@/utils/log';
import { Divider } from '@mui/material';
import CircularProgress from '@mui/material/CircularProgress';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import RecordNotFound from 'src/components/_shared/not-found/RecordNotFound';
import * as Y from 'yjs';
function DatabaseRow({ rowId }: { rowId: string }) {
const { objectId, workspaceId } = useId() || {};
const [doc, setDoc] = useState<YDoc | null>(null);
const [rows, setRows] = useState<Y.Map<YDoc> | null>(null); // Map<rowId, YDoc
const databaseService = useContext(AFConfigContext)?.service?.databaseService;
const [notFound, setNotFound] = useState<boolean>(false);
const handleOpenDatabaseRow = useCallback(async () => {
if (!databaseService || !workspaceId || !objectId) return;
try {
setDoc(null);
const { databaseDoc, rows } = await databaseService.openDatabase(workspaceId, objectId, [rowId]);
console.log('database', databaseDoc.getMap(YjsEditorKey.data_section).toJSON());
console.log('row', rows.get(rowId)?.getMap(YjsEditorKey.data_section).toJSON());
const row = rows.get(rowId);
if (!row) {
setNotFound(true);
return;
}
setDoc(databaseDoc);
setRows(rows);
} catch (e) {
Log.error(e);
setNotFound(true);
}
}, [databaseService, workspaceId, objectId, rowId]);
useEffect(() => {
setNotFound(false);
void handleOpenDatabaseRow();
}, [handleOpenDatabaseRow]);
if (notFound || !objectId) {
return <RecordNotFound open={notFound} workspaceId={workspaceId} />;
}
if (!rows || !doc) {
return (
<div className={'flex h-full w-full items-center justify-center'}>
<CircularProgress />
</div>
);
}
return (
<div className={'flex w-full flex-col items-center'}>
<div className={'max-w-screen relative flex w-[964px] min-w-0 flex-col gap-4'}>
<DatabaseContextProvider viewId={objectId} doc={doc} rowDocMap={rows} readOnly={true}>
<DatabaseRowHeader rowId={rowId} />
<div className={'flex flex-1 flex-col gap-4'}>
<DatabaseRowProperties rowId={rowId} />
<Divider className={'mx-16 max-md:mx-4'} />
<DatabaseRowSubDocument rowId={rowId} />
</div>
</DatabaseContextProvider>
</div>
</div>
);
}
export default DatabaseRow;

View File

@ -6,7 +6,7 @@ function DatabaseTitle({ viewId }: { viewId: string }) {
return (
<div className={'flex w-full flex-col py-4'}>
<div className={'flex w-full items-center px-24 max-md:px-4'}>
<div className={'flex w-full items-center px-16 max-md:px-4'}>
<div className={'flex items-center gap-2 text-3xl'}>
<div>{icon}</div>
<div className={'font-bold'}>{name}</div>

View File

@ -1,4 +1,3 @@
import { AFScroller } from '@/components/_shared/scroller';
import { useCalendarSetup } from '@/components/database/calendar/Calendar.hooks';
import { Toolbar, Event } from '@/components/database/components/calendar';
import React from 'react';
@ -9,24 +8,25 @@ export function Calendar() {
const { dayPropGetter, localizer, formats, events, emptyEvents } = useCalendarSetup();
return (
<AFScroller className={'appflowy-calendar'}>
<div className={'h-full max-h-[960px] min-h-[560px] px-24 py-4 max-md:px-4'}>
<BigCalendar
components={{
toolbar: (props) => <Toolbar {...props} emptyEvents={emptyEvents} />,
eventWrapper: Event,
}}
events={events}
views={['month']}
localizer={localizer}
formats={formats}
dayPropGetter={dayPropGetter}
showMultiDayTimes={true}
step={1}
showAllEvents={true}
/>
</div>
</AFScroller>
<div className={'appflowy-calendar h-full max-h-[960px] min-h-[560px] px-16 pt-4 max-md:px-4'}>
<BigCalendar
components={{
toolbar: (props) => <Toolbar {...props} emptyEvents={emptyEvents} />,
eventWrapper: Event,
}}
style={{
marginBottom: '24px',
}}
events={events}
views={['month']}
localizer={localizer}
formats={formats}
dayPropGetter={dayPropGetter}
showMultiDayTimes={true}
step={1}
showAllEvents={true}
/>
</div>
);
}

View File

@ -12,6 +12,7 @@ $today-highlight-bg: transparent;
.rbc-date-cell {
min-width: 100px;
max-width: 180px;
}
.rbc-date-cell.rbc-now {
@ -25,19 +26,38 @@ $today-highlight-bg: transparent;
.rbc-month-view {
border: none;
@apply h-full overflow-auto;
.rbc-month-row {
border: 1px solid var(--line-divider);
border-bottom: none;
border-top: none;
}
&:last-child {
border-bottom: 1px solid var(--line-divider);
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}
&:hover {
&::-webkit-scrollbar-thumb, & *::-webkit-scrollbar-thumb {
border-radius: 4px;
background-color: var(--scrollbar-thumb);
}
}
}
.rbc-month-header {
height: 40px;
position: sticky;
top: 0;
background: var(--bg-body);
z-index: 50;
@apply border-b border-line-divider;
.rbc-header {
border: none;
@ -60,4 +80,10 @@ $today-highlight-bg: transparent;
display: inline-table !important;
flex: 0 0 0 !important;
min-height: 120px !important;
}
.event-properties {
.property-label {
@apply text-text-caption;
}
}

View File

@ -16,7 +16,7 @@ export const Group = ({ groupId }: GroupProps) => {
if (notFound) {
return (
<div className={'mt-[10%] flex h-full w-full flex-col items-center gap-2 px-24 text-text-caption max-md:px-4'}>
<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('board.noGroup')}</div>
<div className={'text-xs'}>{t('board.noGroupDesc')}</div>
</div>
@ -25,7 +25,7 @@ export const Group = ({ groupId }: GroupProps) => {
if (columns.length === 0 || !fieldId) return null;
return (
<AFScroller overflowYHidden className={'relative px-24 max-md:px-4'}>
<AFScroller overflowYHidden className={'relative px-16 max-md:px-4'}>
<Droppable droppableId={`group-${groupId}`} direction='horizontal' type='column'>
{(provided) => {
return (

View File

@ -1,4 +1,4 @@
import { useFieldsSelector } from '@/application/database-yjs';
import { useFieldsSelector, useNavigateToRow } from '@/application/database-yjs';
import { Property } from '@/components/database/components/property';
import { IconButton } from '@mui/material';
import React from 'react';
@ -6,16 +6,22 @@ import { ReactComponent as ExpandMoreIcon } from '$icons/16x/full_view.svg';
function EventPaper({ rowId }: { rowId: string }) {
const fields = useFieldsSelector();
const navigateToRow = useNavigateToRow();
return (
<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 w-full items-center justify-end'}>
<IconButton size={'small'}>
<IconButton
onClick={() => {
navigateToRow?.(rowId);
}}
size={'small'}
>
<ExpandMoreIcon />
</IconButton>
</div>
<div className={'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) => {
return <Property fieldId={field.fieldId} rowId={rowId} key={field.fieldId} />;
})}

View File

@ -1,33 +1,28 @@
import { YjsDatabaseKey } from '@/application/collab.type';
import { useCellSelector, useDatabase } from '@/application/database-yjs';
import React, { useEffect } from 'react';
import { useCellSelector, useNavigateToRow, usePrimaryFieldId } from '@/application/database-yjs';
import { Cell } from '@/components/database/components/cell';
import React from 'react';
import { useTranslation } from 'react-i18next';
import Cell from 'src/components/database/components/cell/Cell';
function NoDateRow({ rowId }: { rowId: string }) {
const database = useDatabase();
const [primaryFieldId, setPrimaryFieldId] = React.useState<string | null>(null);
const navigateToRow = useNavigateToRow();
const primaryFieldId = usePrimaryFieldId();
const cell = useCellSelector({
rowId,
fieldId: primaryFieldId || '',
});
const { t } = useTranslation();
useEffect(() => {
const fields = database?.get(YjsDatabaseKey.fields);
const primaryFieldId = Array.from(fields?.keys() || []).find((fieldId) => {
return fields?.get(fieldId)?.get(YjsDatabaseKey.is_primary);
});
setPrimaryFieldId(primaryFieldId || null);
}, [database]);
if (!primaryFieldId || !cell?.data) {
return <div className={'text-xs text-text-caption'}>{t('grid.row.titlePlaceholder')}</div>;
}
return (
<div className={'w-full hover:text-fill-default'}>
<div
onClick={() => {
navigateToRow?.(rowId);
}}
className={'w-full hover:text-fill-default'}
>
<Cell
style={{
cursor: 'pointer',

View File

@ -17,6 +17,7 @@ export function Cell(props: CellProps<CellType>) {
const { cell, rowId, fieldId, style } = props;
const { field } = useFieldSelector(fieldId);
const fieldType = Number(field?.get(YjsDatabaseKey.type)) as FieldType;
const Component = useMemo(() => {
switch (fieldType) {
case FieldType.RichText:

View File

@ -3,7 +3,7 @@ import { CellProps, ChecklistCell as ChecklistCellType } from '@/components/data
import LinearProgressWithLabel from '@/components/_shared/progress/LinearProgressWithLabel';
import React, { useMemo } from 'react';
export function ChecklistCell({ cell, style }: CellProps<ChecklistCellType>) {
export function ChecklistCell({ cell, style, placeholder }: CellProps<ChecklistCellType>) {
const data = useMemo(() => {
return parseChecklistData(cell?.data ?? '');
}, [cell?.data]);
@ -11,7 +11,12 @@ export function ChecklistCell({ cell, style }: CellProps<ChecklistCellType>) {
const options = data?.options;
const selectedOptions = data?.selectedOptionIds;
if (!data || !options || !selectedOptions) return null;
if (!data || !options || !selectedOptions)
return placeholder ? (
<div style={style} className={'text-text-placeholder'}>
{placeholder}
</div>
) : null;
return (
<div style={style} className={'w-full cursor-pointer'}>
<LinearProgressWithLabel value={data?.percentage} count={options.length} selectedCount={selectedOptions.length} />

View File

@ -1,5 +1,5 @@
import { YjsDatabaseKey } from '@/application/collab.type';
import { useRowMeta } from '@/application/database-yjs';
import { useRowData } from '@/application/database-yjs';
import { useDateTypeCellDispatcher } from '@/components/database/components/cell/Cell.hooks';
import React, { useEffect, useMemo, useState } from 'react';
@ -15,24 +15,24 @@ export function RowCreateModifiedTime({
attrName: YjsDatabaseKey.last_modified | YjsDatabaseKey.created_at;
}) {
const { getDateTimeStr } = useDateTypeCellDispatcher(fieldId);
const rowMeta = useRowMeta(rowId);
const rowData = useRowData(rowId);
const [value, setValue] = useState<string | null>(null);
useEffect(() => {
if (!rowMeta) return;
if (!rowData) return;
const observeHandler = () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
setValue(rowMeta.get(attrName));
setValue(rowData.get(attrName));
};
observeHandler();
rowMeta.observe(observeHandler);
rowData.observe(observeHandler);
return () => {
rowMeta.unobserve(observeHandler);
rowData.unobserve(observeHandler);
};
}, [rowMeta, attrName]);
}, [rowData, attrName]);
const time = useMemo(() => {
if (!value) return null;
@ -40,7 +40,11 @@ export function RowCreateModifiedTime({
}, [value, getDateTimeStr]);
if (!time) return null;
return <div style={style}>{time}</div>;
return (
<div style={style} className={'flex w-full items-center'}>
{time}
</div>
);
}
export default RowCreateModifiedTime;

View File

@ -0,0 +1,46 @@
import { ReactComponent as ExpandMoreIcon } from '$icons/16x/full_view.svg';
import { useNavigateToRow, useRowMetaSelector } from '@/application/database-yjs';
import { TextCell as CellType, CellProps } from '@/components/database/components/cell/cell.type';
import { TextCell } from '@/components/database/components/cell/text';
import { Tooltip } from '@mui/material';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
export function PrimaryCell(props: CellProps<CellType>) {
const navigateToRow = useNavigateToRow();
const { rowId } = props;
// const icon = null;
const icon = useRowMetaSelector(rowId)?.icon;
const [hover, setHover] = useState(false);
const { t } = useTranslation();
return (
<div
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>}
<TextCell {...props} />
{hover && (
<Tooltip placement={'bottom'} title={t('tooltip.openAsPage')}>
<button
color={'primary'}
className={
'absolute right-0 top-1/2 min-w-0 -translate-y-1/2 transform rounded border border-line-divider bg-bg-body p-1 hover:bg-fill-list-hover'
}
onClick={() => {
navigateToRow?.(rowId);
}}
>
<ExpandMoreIcon />
</button>
</Tooltip>
)}
</div>
);
}
export default PrimaryCell;

View File

@ -0,0 +1 @@
export * from './PrimaryCell';

View File

@ -18,7 +18,7 @@ function RelationItems({ style, cell, fieldId }: { cell: RelationCell; fieldId:
useEffect(() => {
if (!workspaceId || !databaseId) return;
void databaseService?.getDatabase(workspaceId, databaseId).then(({ databaseDoc: doc, rows }) => {
void databaseService?.getDatabase(workspaceId, databaseId, rowIds).then(({ databaseDoc: doc, rows }) => {
const fields = doc
.getMap(YjsEditorKey.data_section)
.get(YjsEditorKey.database)
@ -32,7 +32,7 @@ function RelationItems({ style, cell, fieldId }: { cell: RelationCell; fieldId:
setRows(rows);
});
}, [workspaceId, databaseId, databaseService]);
}, [workspaceId, databaseId, databaseService, rowIds]);
return (
<div style={style} className={'flex items-center gap-2'}>

View File

@ -5,7 +5,7 @@ import { CellProps, SelectOptionCell as SelectOptionCellType } from '@/component
import React, { useCallback, useMemo } from 'react';
export function SelectOptionCell({ cell, fieldId, style, placeholder }: CellProps<SelectOptionCellType>) {
const selectOptionIds = useMemo(() => cell?.data.split(','), [cell]);
const selectOptionIds = useMemo(() => (!cell?.data ? [] : cell?.data.split(',')), [cell]);
const { field } = useFieldSelector(fieldId);
const typeOption = useMemo(() => {
if (!field) return null;

View File

@ -9,7 +9,7 @@ export function UrlCell({ cell, style, placeholder }: CellProps<UrlCellType>) {
const isUrl = useMemo(() => (cell ? processUrl(cell.data) : false), [cell]);
const className = useMemo(() => {
const classList = ['select-text', 'w-fit'];
const classList = ['select-text', 'w-fit', 'flex', 'w-full', 'items-center'];
if (isUrl) {
classList.push('text-content-blue-400', 'underline', 'cursor-pointer');

View File

@ -18,7 +18,7 @@ export function DatabaseConditions() {
borderTopWidth: expanded ? '1px' : '0',
}}
className={
'database-conditions relative mx-24 transform overflow-hidden border-t border-line-divider transition-all max-md:mx-4'
'database-conditions relative mx-16 transform overflow-hidden border-t border-line-divider transition-all max-md:mx-4'
}
>
<AFScroller overflowYHidden className={'flex items-center gap-2'}>

View File

@ -0,0 +1,18 @@
import { useFieldsSelector, usePrimaryFieldId } from '@/application/database-yjs';
import { Property } from '@/components/database/components/property';
import React from 'react';
export function DatabaseRowProperties({ rowId }: { rowId: string }) {
const primaryFieldId = usePrimaryFieldId();
const fields = useFieldsSelector().filter((column) => column.fieldId !== primaryFieldId);
return (
<div className={'row-properties flex w-full flex-1 flex-col gap-4 px-16 py-2 max-md:px-4'}>
{fields.map((field) => {
return <Property fieldId={field.fieldId} rowId={rowId} key={field.fieldId} />;
})}
</div>
);
}
export default DatabaseRowProperties;

View File

@ -0,0 +1,53 @@
import { YDoc } from '@/application/collab.type';
import { useRowMetaSelector } from '@/application/database-yjs';
import { useId } from '@/components/_shared/context-provider/IdProvider';
import { AFConfigContext } from '@/components/app/AppConfig';
import { Editor } from '@/components/editor';
import { Log } from '@/utils/log';
import CircularProgress from '@mui/material/CircularProgress';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import RecordNotFound from '@/components/_shared/not-found/RecordNotFound';
export function DatabaseRowSubDocument({ rowId }: { rowId: string }) {
const { workspaceId } = useId() || {};
const documentId = useRowMetaSelector(rowId)?.documentId;
const [doc, setDoc] = useState<YDoc | null>(null);
const [notFound, setNotFound] = useState<boolean>(false);
const documentService = useContext(AFConfigContext)?.service?.documentService;
const handleOpenDocument = useCallback(async () => {
if (!documentService || !workspaceId || !documentId) return;
try {
setDoc(null);
const doc = await documentService.openDocument(workspaceId, documentId);
setDoc(doc);
} catch (e) {
Log.error(e);
setNotFound(true);
}
}, [documentService, workspaceId, documentId]);
useEffect(() => {
setNotFound(false);
void handleOpenDocument();
}, [handleOpenDocument]);
if (notFound || !documentId) {
return <RecordNotFound open={notFound} workspaceId={workspaceId} />;
}
if (!doc) {
return (
<div className={'flex h-full w-full items-center justify-center'}>
<CircularProgress />
</div>
);
}
return <Editor doc={doc} readOnly={true} includeRoot={false} />;
}
export default DatabaseRowSubDocument;

View File

@ -0,0 +1,2 @@
export * from './DatabaseRowProperties';
export * from './DatabaseRowSubDocument';

View File

@ -1,8 +1,10 @@
import { FieldId } from '@/application/collab.type';
import { FieldId, YjsDatabaseKey } from '@/application/collab.type';
import { useCellSelector } from '@/application/database-yjs';
import { useFieldSelector } from '@/application/database-yjs/selector';
import { Cell } from '@/components/database/components/cell';
import React, { useEffect } from 'react';
import { CellProps, Cell as CellType } from '@/components/database/components/cell/cell.type';
import { PrimaryCell } from '@/components/database/components/cell/primary';
import React, { useEffect, useMemo, useRef } from 'react';
export interface GridCellProps {
rowId: string;
@ -13,8 +15,9 @@ export interface GridCellProps {
}
export function GridCell({ onResize, rowId, fieldId, columnIndex, rowIndex }: GridCellProps) {
const ref = React.useRef<HTMLDivElement>(null);
const field = useFieldSelector(fieldId);
const ref = useRef<HTMLDivElement>(null);
const { field } = useFieldSelector(fieldId);
const isPrimary = field?.get(YjsDatabaseKey.is_primary);
const cell = useCellSelector({
rowId,
fieldId,
@ -23,15 +26,13 @@ export function GridCell({ onResize, rowId, fieldId, columnIndex, rowIndex }: Gr
useEffect(() => {
const el = ref.current;
if (!el) return;
if (!el || !cell) return;
const observer = new ResizeObserver(() => {
if (onResize) {
onResize(rowIndex, columnIndex, {
width: el.offsetWidth,
height: el.offsetHeight,
});
}
onResize?.(rowIndex, columnIndex, {
width: el.offsetWidth,
height: el.offsetHeight,
});
});
observer.observe(el);
@ -39,12 +40,21 @@ export function GridCell({ onResize, rowId, fieldId, columnIndex, rowIndex }: Gr
return () => {
observer.disconnect();
};
}, [columnIndex, onResize, rowIndex]);
}, [columnIndex, onResize, rowIndex, cell]);
const Component = useMemo(() => {
if (isPrimary) {
return PrimaryCell;
}
return Cell;
}, [isPrimary]) as React.FC<CellProps<CellType>>;
if (!field) return null;
return (
<div ref={ref} className={'grid-cell w-full cursor-text overflow-hidden text-xs'}>
<Cell cell={cell} rowId={rowId} fieldId={fieldId} />
<div ref={ref} className={'grid-cell flex min-h-full w-full cursor-text items-center overflow-hidden text-xs'}>
<Component cell={cell} rowId={rowId} fieldId={fieldId} />
</div>
);
}

View File

@ -20,7 +20,6 @@ export type RenderColumn = {
export function useRenderFields() {
const fields = useFieldsSelector();
console.log('columns', fields);
const renderColumns = useMemo(() => {
const data = fields.map((column) => ({
...column,
@ -30,7 +29,7 @@ export function useRenderFields() {
return [
{
type: GridColumnType.Action,
width: 96,
width: 64,
},
...data,
{
@ -39,7 +38,7 @@ export function useRenderFields() {
},
{
type: GridColumnType.Action,
width: 96,
width: 64,
},
].filter(Boolean) as RenderColumn[];
}, [fields]);

View File

@ -0,0 +1,11 @@
import { usePageInfo } from '@/components/_shared/page/usePageInfo';
import Title from './Title';
import React from 'react';
export function DatabaseHeader({ viewId }: { viewId: string }) {
const { name, icon } = usePageInfo(viewId);
return <Title name={name} icon={icon} />;
}
export default DatabaseHeader;

View File

@ -0,0 +1,16 @@
import { useCellSelector, usePrimaryFieldId, useRowMetaSelector } from '@/application/database-yjs';
import Title from '@/components/database/components/header/Title';
import React from 'react';
function DatabaseRowHeader({ rowId }: { rowId: string }) {
const fieldId = usePrimaryFieldId() || '';
const meta = useRowMetaSelector(rowId);
const cell = useCellSelector({
rowId,
fieldId,
});
return <Title icon={meta?.icon} name={cell?.data as string} />;
}
export default DatabaseRowHeader;

View File

@ -0,0 +1,19 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
export function Title({ icon, name }: { icon?: string; name?: string }) {
const { t } = useTranslation();
return (
<div className={'flex w-full flex-col py-4'}>
<div className={'flex w-full items-center px-16 max-md:px-4'}>
<div className={'flex items-center gap-2 text-3xl'}>
<div>{icon}</div>
<div className={'font-bold'}>{name || t('document.title.placeholder')}</div>
</div>
</div>
</div>
);
}
export default Title;

View File

@ -0,0 +1,2 @@
export * from './DatabaseHeader';
export * from './DatabaseRowHeader';

View File

@ -2,7 +2,6 @@ import { YjsDatabaseKey } from '@/application/collab.type';
import { FieldType, useCellSelector, useFieldSelector } from '@/application/database-yjs';
import { Cell as CellType, CellProps, TextCell } from '@/components/database/components/cell/cell.type';
import { CheckboxCell } from '@/components/database/components/cell/checkbox';
import { ChecklistCell } from '@/components/database/components/cell/checklist';
import { RowCreateModifiedTime } from '@/components/database/components/cell/created-modified';
import { DateTimeCell } from '@/components/database/components/cell/date';
import { NumberCell } from '@/components/database/components/cell/number';
@ -11,6 +10,7 @@ import { SelectOptionCell } from '@/components/database/components/cell/select-o
import { UrlCell } from '@/components/database/components/cell/url';
import PropertyWrapper from '@/components/database/components/property/PropertyWrapper';
import { TextProperty } from '@/components/database/components/property/text';
import { ChecklistProperty } from 'src/components/database/components/property/cheklist';
import React, { FC, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -39,7 +39,7 @@ export function Property({ fieldId, rowId }: { fieldId: string; rowId: string })
case FieldType.DateTime:
return DateTimeCell;
case FieldType.Checklist:
return ChecklistCell;
return ChecklistProperty;
case FieldType.Relation:
return RelationCell;
default:

View File

@ -3,8 +3,8 @@ import React from 'react';
function PropertyWrapper({ fieldId, children }: { fieldId: string; children: React.ReactNode }) {
return (
<div className={'flex w-full items-center gap-2'}>
<div className={'w-[100px] text-text-caption'}>
<div className={'flex min-h-[28px] w-full gap-2'}>
<div className={'property-label flex h-[28px] w-[30%] items-center'}>
<FieldDisplay fieldId={fieldId} />
</div>
<div className={'flex flex-1 flex-wrap pr-1'}>{children}</div>

View File

@ -0,0 +1,34 @@
import { parseChecklistData } from '@/application/database-yjs';
import { CellProps, ChecklistCell as CellType } from '@/components/database/components/cell/cell.type';
import { ChecklistCell } from '@/components/database/components/cell/checklist';
import React, { useMemo } from 'react';
import { ReactComponent as CheckboxCheckSvg } from '$icons/16x/check_filled.svg';
import { ReactComponent as CheckboxUncheckSvg } from '$icons/16x/uncheck.svg';
export function ChecklistProperty(props: CellProps<CellType>) {
const { cell } = props;
const data = useMemo(() => {
return parseChecklistData(cell?.data ?? '');
}, [cell?.data]);
const options = data?.options;
const selectedOptions = data?.selectedOptionIds;
return (
<div className={'flex w-full flex-col gap-2'}>
<ChecklistCell {...props} />
{options?.map((option) => {
const isSelected = selectedOptions?.includes(option.id);
return (
<div key={option.id} className={'flex items-center gap-2 text-xs font-medium'}>
{isSelected ? <CheckboxCheckSvg className={'h-4 w-4'} /> : <CheckboxUncheckSvg className={'h-4 w-4'} />}
<div>{option.name}</div>
</div>
);
})}
</div>
);
}
export default ChecklistProperty;

View File

@ -0,0 +1 @@
export * from './ChecklistProperty';

View File

@ -54,7 +54,7 @@ export const DatabaseTabs = forwardRef<HTMLDivElement, DatabaseTabBarProps>(
return (
<div
ref={ref}
className='mx-24 -mb-[0.5px] flex items-center overflow-hidden border-b border-line-divider text-text-title max-md:mx-4'
className='mx-16 -mb-[0.5px] flex items-center overflow-hidden border-b border-line-divider text-text-title max-md:mx-4'
>
<div
style={{

View File

@ -1,3 +1,4 @@
import { lazy } from 'react';
export const Database = lazy(() => import('./Database'));
export const DatabaseRow = lazy(() => import('./DatabaseRow'));

View File

@ -41,7 +41,7 @@ export const Document = () => {
<DocumentHeader doc={doc} viewId={documentId} />
<div className={'flex w-full justify-center'}>
<div className={'max-w-screen w-[964px] min-w-0'}>
<Editor doc={doc} readOnly={true} />
<Editor doc={doc} readOnly={true} includeRoot={true} />
</div>
</div>
</div>

View File

@ -13,18 +13,27 @@ import * as Y from 'yjs';
const defaultInitialValue: Descendant[] = [];
function CollaborativeEditor({ doc }: { doc: Y.Doc }) {
const context = useEditorContext();
// if readOnly, collabOrigin is Local, otherwise RemoteSync
const collabOrigin = context.readOnly ? CollabOrigin.Local : CollabOrigin.LocalSync;
const editor = useMemo(
() => doc && (withPlugins(withReact(withYjs(createEditor(), doc, collabOrigin))) as YjsEditor),
[doc, collabOrigin]
);
const [connected, setIsConnected] = useState(false);
function CollaborativeEditor({ doc, includeRoot = true }: { doc: Y.Doc; includeRoot?: boolean }) {
const viewId = useId()?.objectId || '';
const { view } = useViewSelector(viewId);
const title = view?.get(YjsFolderKey.name);
const title = includeRoot ? view?.get(YjsFolderKey.name) : undefined;
const context = useEditorContext();
// if readOnly, collabOrigin is Local, otherwise RemoteSync
const localOrigin = context.readOnly ? CollabOrigin.Local : CollabOrigin.LocalSync;
const editor = useMemo(
() =>
doc &&
(withPlugins(
withReact(
withYjs(createEditor(), doc, {
localOrigin,
includeRoot,
})
)
) as YjsEditor),
[doc, localOrigin, includeRoot]
);
const [connected, setIsConnected] = useState(false);
useEffect(() => {
if (!editor) return;
@ -37,8 +46,8 @@ function CollaborativeEditor({ doc }: { doc: Y.Doc }) {
}, [editor]);
useEffect(() => {
if (!editor || !connected) return;
CustomEditor.setDocumentTitle(editor, title || '');
if (!editor || !connected || title === undefined) return;
CustomEditor.setDocumentTitle(editor, title);
}, [editor, title, connected]);
return (

View File

@ -4,10 +4,18 @@ import { EditorContextProvider } from '@/components/editor/EditorContext';
import React from 'react';
import './editor.scss';
export const Editor = ({ readOnly, doc }: { readOnly: boolean; doc: YDoc }) => {
export const Editor = ({
readOnly,
doc,
includeRoot = true,
}: {
readOnly: boolean;
doc: YDoc;
includeRoot?: boolean;
}) => {
return (
<EditorContextProvider readOnly={readOnly}>
<CollaborativeEditor doc={doc} />
<CollaborativeEditor doc={doc} includeRoot={includeRoot} />
</EditorContextProvider>
);
};

View File

@ -10,7 +10,10 @@ export const Callout = memo(
<CalloutIcon node={node} />
</div>
<div ref={ref} className={`${attributes.className ?? ''} w-full bg-bg-body py-2`}>
<div {...attributes} className={`flex w-full flex-col rounded bg-content-blue-50 py-2 pl-10`}>
<div
{...attributes}
className={`flex w-full flex-col rounded border border-line-divider bg-fill-list-active py-2 pl-10`}
>
{children}
</div>
</div>

View File

@ -6,7 +6,7 @@ function CalloutIcon({ node }: { node: CalloutNode }) {
return (
<>
<span contentEditable={false} ref={ref} className={`h-8 w-8 p-1`}>
<span contentEditable={false} ref={ref} className={`flex h-8 w-8 items-center p-1`}>
{node.data.icon}
</span>
</>

View File

@ -15,7 +15,7 @@ export const CodeBlock = memo(
<div {...attributes} ref={ref} className={`${attributes.className ?? ''} flex w-full bg-bg-body py-2`}>
<pre
spellCheck={false}
className={`flex w-full rounded border border-solid border-line-divider bg-content-blue-50 p-5 pt-20`}
className={`flex w-full rounded border border-line-divider bg-fill-list-active p-5 pt-20`}
>
<code>{children}</code>
</pre>

View File

@ -20,7 +20,7 @@ export const MathEquation = memo(
>
<div
contentEditable={false}
className={`container-bg w-full select-none rounded border border-line-divider bg-content-blue-50 px-3`}
className={`container-bg w-full select-none rounded border border-line-divider bg-fill-list-active px-3`}
>
{formula ? (
<KatexMath latex={formula} />

View File

@ -197,12 +197,14 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) {
.bulleted-icon {
&:after {
content: attr(data-letter);
font-weight: 500;
}
}
.numbered-icon {
&:after {
content: attr(data-number) ".";
font-weight: 500;
}
}
@ -238,7 +240,7 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) {
&:hover {
.container-bg {
background: var(--content-blue-100) !important;
background: var(--fill-list-hover) !important;
}
}
}
@ -270,4 +272,8 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) {
@apply ml-5;
}
}
.text-block-icon {
@apply flex items-center justify-center;
}

View File

@ -1,7 +1,7 @@
import { layoutMap, ViewLayout, YjsFolderKey } from '@/application/collab.type';
import React from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import Page from 'src/components/_shared/page/Page';
import Page from '@/components/_shared/page/Page';
function ViewItem({ id }: { id: string }) {
const navigate = useNavigate();

View File

@ -19,6 +19,7 @@ function Layout({ children }: { children: React.ReactNode }) {
if (!folder) return;
console.log(folder.toJSON());
setFolder(folder);
},
[folderService]

View File

@ -45,6 +45,7 @@
opacity: 60%;
}
.workspaces, .database-conditions, .grid-scroll-table, .grid-board, .MuiPaper-root, .appflowy-database {
::-webkit-scrollbar {
width: 0;
@ -58,9 +59,6 @@
font-family: "Apple Color Emoji", "Segoe UI Emoji", NotoColorEmoji, "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols;
line-height: 1em;
white-space: nowrap;
//&:hover {
// background-color: rgba(156, 156, 156, 0.20);
//}
}
.theme-mode-item {
@ -97,4 +95,4 @@
backgroundColor: var(--bg-body);
transform: rotate(45deg);
}
}
}

View File

@ -1,10 +1,16 @@
import { Database } from '@/components/database';
import { Database, DatabaseRow } from '@/components/database';
import React from 'react';
import { useSearchParams } from 'react-router-dom';
function DatabasePage () {
return (
<Database />
);
function DatabasePage() {
const [search] = useSearchParams();
const rowId = search.get('r');
if (rowId) {
return <DatabaseRow rowId={rowId} />;
}
return <Database />;
}
export default DatabasePage;
export default DatabasePage;

View File

@ -31,21 +31,6 @@ textarea {
}
::-webkit-scrollbar {
width: 8px;
}
:root[data-dark-mode=true] body {
scrollbar-color: #fff var(--bg-body);
}
body {
scrollbar-track-color: var(--bg-body);
scrollbar-shadow-color: var(--bg-body);
}
.btn {
@apply rounded-xl border border-line-divider px-4 py-3;
}

View File

@ -71,6 +71,9 @@ export default defineConfig({
cors: false,
},
envPrefix: ['AF', 'TAURI_'],
esbuild: {
drop: ['console', 'debugger'],
},
build: !!process.env.TAURI_PLATFORM
? {
// Tauri supports es2021
@ -82,15 +85,6 @@ export default defineConfig({
}
: {
target: `esnext`,
terserOptions: !isDev
? {
compress: {
keep_infinity: true,
drop_console: true,
drop_debugger: true,
},
}
: {},
reportCompressedSize: true,
sourcemap: isDev,
rollupOptions: !isDev