From 7867f0366e8db0879c7b7fa34322c3ac0460169b Mon Sep 17 00:00:00 2001 From: "Kilu.He" <108015703+qinluhe@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:16:32 +0800 Subject: [PATCH] feat: support the operations of field in the grid of tauri (#3906) * feat: support the operations of field in the grid of tauri * fix: performance optimizate --- .../src/appflowy_app/assets/board.svg | 16 ++++ .../src/appflowy_app/assets/document.svg | 14 +++ .../src/appflowy_app/assets/grid.svg | 6 ++ .../components/database/Database.hooks.ts | 18 +++- .../components/database/Database.tsx | 9 +- .../application/field/field_listeners.ts | 11 +++ .../application/field/field_service.ts | 95 +++++++++++++++---- .../database/application/field/field_types.ts | 11 +-- .../database/application/field/index.ts | 1 + .../database/components/cell/TextCell.tsx | 11 ++- .../database/components/field/FieldSelect.tsx | 25 ++--- .../database/components/field/FieldsMenu.tsx | 4 +- .../components/tab_bar/DatabaseTabBar.tsx | 39 +++++--- .../grid/GridCalculate/GridCalculate.tsx | 30 ++++++ .../database/grid/GridCalculate/index.ts | 1 + .../database/grid/GridField/GridField.tsx | 18 +++- .../database/grid/GridField/GridFieldMenu.tsx | 47 +++++---- .../grid/GridField/GridFieldMenuActions.tsx | 72 ++++++++++++-- .../database/grid/GridField/GridResizer.tsx | 92 ++++++++++++++++++ .../grid/GridRow/GridCalculateRow.tsx | 20 +++- .../GridRow/GridCellRow/GridCellRow.hooks.ts | 7 ++ .../grid/GridRow/GridCellRow/GridCellRow.tsx | 27 +++--- .../database/grid/GridRow/GridFieldRow.tsx | 29 +++--- .../database/grid/GridRow/GridRow.tsx | 10 +- .../database/grid/GridRow/constants.ts | 16 ++-- .../database/grid/GridTable/GridTable.tsx | 12 ++- .../components/document/CodeBlock/index.tsx | 4 +- .../document/EquationBlock/index.tsx | 2 +- .../components/document/GridBlock/index.tsx | 2 +- .../components/document/ImageBlock/index.tsx | 2 +- .../components/document/Node/NodeChildren.tsx | 2 +- .../components/document/Node/index.tsx | 4 +- .../components/document/Root/index.tsx | 4 +- .../appflowy_app/hooks/notification.hooks.ts | 2 + 34 files changed, 512 insertions(+), 151 deletions(-) create mode 100644 frontend/appflowy_tauri/src/appflowy_app/assets/board.svg create mode 100644 frontend/appflowy_tauri/src/appflowy_app/assets/document.svg create mode 100644 frontend/appflowy_tauri/src/appflowy_app/assets/grid.svg create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_listeners.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/database/grid/GridCalculate/GridCalculate.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/database/grid/GridCalculate/index.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/database/grid/GridField/GridResizer.tsx diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/board.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/board.svg new file mode 100644 index 0000000000..0bb0e3fabe --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/assets/board.svg @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/document.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/document.svg new file mode 100644 index 0000000000..b00e1cfb38 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/assets/document.svg @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/grid.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/grid.svg new file mode 100644 index 0000000000..c397af8130 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/assets/grid.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.hooks.ts index 847d5aa03b..a69942e716 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.hooks.ts @@ -1,9 +1,9 @@ -import { createContext, useContext, useCallback, useMemo, useEffect, useState, useRef } from 'react'; +import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { proxy, useSnapshot } from 'valtio'; -import { DatabaseLayoutPB, DatabaseNotification } from '@/services/backend'; +import { DatabaseLayoutPB, DatabaseNotification, FieldVisibility } from '@/services/backend'; import { subscribeNotifications } from '$app/hooks'; -import { Database, databaseService, fieldService, rowListeners, sortListeners } from './application'; +import { Database, databaseService, fieldListeners, fieldService, rowListeners, sortListeners } from './application'; export function useSelectDatabaseView({ viewId }: { viewId?: string }) { const key = 'v'; @@ -40,6 +40,15 @@ export const DatabaseProvider = DatabaseContext.Provider; export const useDatabase = () => useSnapshot(useContext(DatabaseContext)); +export const useDatabaseVisibilityFields = () => { + const database = useDatabase(); + + return useMemo( + () => database.fields.filter((field) => field.visibility !== FieldVisibility.AlwaysHidden), + [database.fields] + ); +}; + export const useConnectDatabase = (viewId: string) => { const database = useMemo(() => { const proxyDatabase = proxy({ @@ -65,6 +74,9 @@ export const useConnectDatabase = (viewId: string) => { [DatabaseNotification.DidUpdateFields]: async () => { database.fields = await fieldService.getFields(viewId); }, + [DatabaseNotification.DidUpdateFieldSettings]: async (changeset) => { + fieldListeners.didUpdateFieldSettings(database, changeset); + }, [DatabaseNotification.DidUpdateViewRows]: (changeset) => { rowListeners.didUpdateViewRows(database, changeset); }, diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.tsx index 997b05ab10..e8e348cd10 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.tsx @@ -52,7 +52,14 @@ export const Database = ({ selectedViewId, setSelectedViewId }: Props) => { selectedViewId={selectedViewId} childViewIds={childViewIds} /> - + {childViewIds.map((id) => ( diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_listeners.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_listeners.ts new file mode 100644 index 0000000000..b000ce5edc --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_listeners.ts @@ -0,0 +1,11 @@ +import { FieldSettingsPB } from '@/services/backend'; +import { Database } from '$app/components/database/application'; + +export function didUpdateFieldSettings(database: Database, settings: FieldSettingsPB) { + const { field_id: fieldId, visibility, width } = settings; + const field = database.fields.find((field) => field.id === fieldId); + + if (!field) return; + field.visibility = visibility; + field.width = width; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_service.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_service.ts index abb0c53f99..d7317db3a6 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_service.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_service.ts @@ -8,6 +8,9 @@ import { MoveFieldPayloadPB, RepeatedFieldIdPB, UpdateFieldTypePayloadPB, + FieldSettingsChangesetPB, + FieldVisibility, + DatabaseViewIdPB, } from '@/services/backend'; import { DatabaseEventDuplicateField, @@ -17,6 +20,8 @@ import { DatabaseEventGetFields, DatabaseEventDeleteField, DatabaseEventCreateTypeOption, + DatabaseEventUpdateFieldSettings, + DatabaseEventGetAllFieldSettings, } from '@/services/backend/events/flowy-database2'; import { Field, pbToField } from './field_types'; import { bytesToTypeOption, getTypeOption } from './type_option'; @@ -24,20 +29,39 @@ import { bytesToTypeOption, getTypeOption } from './type_option'; export async function getFields(viewId: string, fieldIds?: string[]): Promise { const payload = GetFieldPayloadPB.fromObject({ view_id: viewId, - field_ids: fieldIds ? RepeatedFieldIdPB.fromObject({ - items: fieldIds.map(fieldId => ({ field_id: fieldId })), - }) : undefined, + field_ids: fieldIds + ? RepeatedFieldIdPB.fromObject({ + items: fieldIds.map((fieldId) => ({ field_id: fieldId })), + }) + : undefined, }); const result = await DatabaseEventGetFields(payload); - const fields = result.map((value) => value.items.map(pbToField)).unwrap(); + const getSettingsPayload = DatabaseViewIdPB.fromObject({ + value: viewId, + }); - await Promise.all(fields.map(async field => { - const typeOption = await getTypeOption(viewId, field.id, field.type); + const settings = await DatabaseEventGetAllFieldSettings(getSettingsPayload); - field.typeOption = typeOption; - })); + if (settings.ok === false || result.ok === false) { + return Promise.reject('Failed to get fields'); + } + + const fields = await Promise.all( + result.val.items.map(async (item) => { + const setting = settings.val.items.find((setting) => setting.field_id === item.id); + const field = pbToField(item); + const typeOption = await getTypeOption(viewId, field.id, field.type); + + return { + ...field, + visibility: setting?.visibility, + width: setting?.width, + typeOption, + }; + }) + ); return fields; } @@ -51,13 +75,14 @@ export async function createField(viewId: string, fieldType?: FieldType, data?: const result = await DatabaseEventCreateTypeOption(payload); - return result.map(value => { - const field = pbToField(value.field); + if (result.ok === false) { + return Promise.reject('Failed to create field'); + } - field.typeOption = bytesToTypeOption(value.type_option_data, field.type); + const field = pbToField(result.val.field); - return field; - }).unwrap(); + field.typeOption = bytesToTypeOption(result.val.type_option_data, field.type); + return field; } export async function duplicateField(viewId: string, fieldId: string): Promise { @@ -68,16 +93,21 @@ export async function duplicateField(viewId: string, fieldId: string): Promise { +export async function updateField( + viewId: string, + fieldId: string, + data: { + name?: string; + desc?: string; + } +): Promise { const payload = FieldChangesetPB.fromObject({ view_id: viewId, field_id: fieldId, @@ -124,3 +154,26 @@ export async function deleteField(viewId: string, fieldId: string): Promise { + const payload = FieldSettingsChangesetPB.fromObject({ + view_id: viewId, + field_id: fieldId, + ...settings, + }); + + const result = await DatabaseEventUpdateFieldSettings(payload); + + if (result.ok === false) { + return Promise.reject('Failed to update field settings'); + } + + return result.val; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_types.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_types.ts index c685e478be..5c6a4f01fd 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_types.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/field_types.ts @@ -1,7 +1,4 @@ -import { - FieldPB, - FieldType, -} from '@/services/backend'; +import { FieldPB, FieldType, FieldVisibility } from '@/services/backend'; import { DateTimeTypeOption, NumberTypeOption, SelectTypeOption } from './type_option/type_option_types'; export interface Field { @@ -9,8 +6,8 @@ export interface Field { name: string; type: FieldType; typeOption?: unknown; - visibility: boolean; - width: number; + visibility?: FieldVisibility; + width?: number; isPrimary: boolean; } @@ -35,7 +32,5 @@ export const pbToField = (pb: FieldPB): Field => ({ id: pb.id, name: pb.name, type: pb.field_type, - visibility: pb.visibility, - width: pb.width, isPrimary: pb.is_primary, }); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/index.ts index 2cb812dc0d..fa993023e1 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/index.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/application/field/index.ts @@ -2,3 +2,4 @@ export * from './select_option'; export * from './type_option'; export * from './field_types'; export * as fieldService from './field_service'; +export * as fieldListeners from './field_listeners'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TextCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TextCell.tsx index d4e9d5a535..48445c91ef 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TextCell.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TextCell.tsx @@ -1,8 +1,9 @@ import { Popover, TextareaAutosize } from '@mui/material'; -import { FC, FormEventHandler, useCallback, useLayoutEffect, useRef, useState } from 'react'; +import { FC, FormEventHandler, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { useViewId } from '$app/hooks'; import { cellService, Field, TextCell as TextCellType } from '../../application'; import { CellText } from '../../_shared'; +import { useGridUIStateDispatcher } from '$app/components/database/proxy/grid/ui_state/actions'; export const TextCell: FC<{ field: Field; @@ -13,7 +14,7 @@ export const TextCell: FC<{ const [editing, setEditing] = useState(false); const [text, setText] = useState(''); const [width, setWidth] = useState(undefined); - + const { setRowHover } = useGridUIStateDispatcher(); const handleClose = () => { if (!cell) return; if (editing) { @@ -41,6 +42,12 @@ export const TextCell: FC<{ } }, [editing]); + useEffect(() => { + if (editing) { + setRowHover(null); + } + }, [editing, setRowHover]); + return ( <> diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field/FieldSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field/FieldSelect.tsx index 7ad17a5a21..bb6c899a00 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field/FieldSelect.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field/FieldSelect.tsx @@ -1,28 +1,31 @@ import { MenuItem, Select, SelectChangeEvent, SelectProps } from '@mui/material'; import { FC, useCallback } from 'react'; import { Field as FieldType } from '../../application'; -import { useDatabase } from '../../Database.hooks'; +import { useDatabaseVisibilityFields } from '../../Database.hooks'; import { Field } from './Field'; export interface FieldSelectProps extends Omit { onChange?: (event: SelectChangeEvent, field: FieldType | undefined) => void; } -export const FieldSelect: FC = ({ - onChange, - ...props -}) => { - const { fields } = useDatabase(); +export const FieldSelect: FC = ({ onChange, ...props }) => { + const fields = useDatabaseVisibilityFields(); - const handleChange = useCallback((event: SelectChangeEvent) => { - const selectedId = event.target.value; + const handleChange = useCallback( + (event: SelectChangeEvent) => { + const selectedId = event.target.value; - onChange?.(event, fields.find(field => field.id === selectedId)); - }, [onChange, fields]); + onChange?.( + event, + fields.find((field) => field.id === selectedId) + ); + }, + [onChange, fields] + ); return (