소스 검색

chore: add column and edit text cell

ascarbek 2 년 전
부모
커밋
c453d734ab

+ 7 - 3
frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useCell.ts

@@ -4,28 +4,32 @@ import { FieldController } from '$app/stores/effects/database/field/field_contro
 import { CellControllerBuilder } from '$app/stores/effects/database/cell/controller_builder';
 import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '$app/../services/backend';
 import { useEffect, useState } from 'react';
+import { CellController } from '$app/stores/effects/database/cell/cell_controller';
 
 export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => {
   const [data, setData] = useState<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
+  const [cellController, setCellController] = useState<CellController<any, any>>();
 
   useEffect(() => {
     if (!cellIdentifier || !cellCache || !fieldController) return;
     const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
-    const cellController = builder.build();
+    const c = builder.build();
+    setCellController(c);
 
     void (async () => {
-      const cellData = await cellController.getCellData();
+      const cellData = await c.getCellData();
       if (cellData.some) {
         setData(cellData.unwrap());
       }
     })();
 
     return () => {
-      void cellController.dispose();
+      void c.dispose();
     };
   }, [cellIdentifier, cellCache, fieldController]);
 
   return {
+    cellController,
     data,
   };
 };

+ 10 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useRow.ts

@@ -3,6 +3,8 @@ import { RowController } from '$app/stores/effects/database/row/row_controller';
 import { RowInfo } from '$app/stores/effects/database/row/row_cache';
 import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
 import { useEffect, useState } from 'react';
+import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
+import { None } from 'ts-results';
 
 export const useRow = (viewId: string, databaseController: DatabaseController, rowInfo: RowInfo) => {
   const [cells, setCells] = useState<{ fieldId: string; cellIdentifier: CellIdentifier }[]>([]);
@@ -38,7 +40,14 @@ export const useRow = (viewId: string, databaseController: DatabaseController, r
     })();
   }, [rowController]);
 
+  const onNewColumnClick = async () => {
+    if (!databaseController) return;
+    const controller = new TypeOptionController(viewId, None);
+    await controller.initialize();
+  };
+
   return {
-    cells: cells,
+    cells,
+    onNewColumnClick,
   };
 };

+ 13 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/CheckboxSvg.tsx

@@ -0,0 +1,13 @@
+export const CheckboxSvg = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <path d='M6.5 8L8.11538 9.5L13.5 4.5' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
+      <path
+        d='M13 8.5V11.8889C13 12.1836 12.8829 12.4662 12.6746 12.6746C12.4662 12.8829 12.1836 13 11.8889 13H4.11111C3.81643 13 3.53381 12.8829 3.32544 12.6746C3.11706 12.4662 3 12.1836 3 11.8889V4.11111C3 3.81643 3.11706 3.53381 3.32544 3.32544C3.53381 3.11706 3.81643 3 4.11111 3H10.2222'
+        stroke='currentColor'
+        strokeLinecap='round'
+        strokeLinejoin='round'
+      />
+    </svg>
+  );
+};

+ 8 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EditorCheckSvg.tsx

@@ -0,0 +1,8 @@
+export const EditorCheckSvg = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <rect x='2' y='2' width='12' height='12' rx='4' fill='#00BCF0' />
+      <path d='M6 8L7.61538 9.5L10.5 6.5' stroke='white' strokeLinecap='round' strokeLinejoin='round' />
+    </svg>
+  );
+};

+ 7 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EditorUncheckSvg.tsx

@@ -0,0 +1,7 @@
+export const EditorUncheckSvg = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <rect x='2.5' y='2.5' width='11' height='11' rx='3.5' stroke='#BDBDBD' />
+    </svg>
+  );
+};

+ 10 - 2
frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx

@@ -7,6 +7,7 @@ import { ViewLayoutTypePB } from '@/services/backend';
 import { DragDropContext } from 'react-beautiful-dnd';
 import { useState } from 'react';
 import { RowInfo } from '$app/stores/effects/database/row/row_cache';
+import { EditRow } from '$app/components/board/EditBoardRow/EditRow';
 
 export const Board = ({ viewId }: { viewId: string }) => {
   const { controller, rows, groups, onNewRowClick, onDragEnd } = useDatabase(viewId, ViewLayoutTypePB.Board);
@@ -39,7 +40,7 @@ export const Board = ({ viewId }: { viewId: string }) => {
               groups &&
               groups.map((group, index) => (
                 <BoardBlock
-                  key={index}
+                  key={group.groupId}
                   viewId={viewId}
                   controller={controller}
                   group={group}
@@ -52,7 +53,14 @@ export const Board = ({ viewId }: { viewId: string }) => {
           </div>
         </div>
       </DragDropContext>
-
+      {controller && showBoardRow && boardRowInfo && (
+        <EditRow
+          onClose={() => setShowBoardRow(false)}
+          viewId={viewId}
+          controller={controller}
+          rowInfo={boardRowInfo}
+        ></EditRow>
+      )}
     </>
   );
 };

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlock.tsx

@@ -51,7 +51,7 @@ export const BoardBlock = ({
                   viewId={viewId}
                   controller={controller}
                   index={index}
-                  key={index}
+                  key={row.row.id}
                   rowInfo={row}
                   onOpenRow={onOpenRow}
                 ></BoardCard>

+ 7 - 1
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardTextCell.tsx

@@ -14,5 +14,11 @@ export const BoardTextCell = ({
 }) => {
   const { data } = useCell(cellIdentifier, cellCache, fieldController);
 
-  return <div>{(data as string | undefined) || ''}</div>;
+  return (
+    <div>
+      {((data as string | undefined) || '').split('\n').map((line, index) => (
+        <div key={index}>{line}</div>
+      ))}
+    </div>
+  );
 };

+ 33 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/EditBoardRow/EditCellText.tsx

@@ -0,0 +1,33 @@
+import { CellController } from '$app/stores/effects/database/cell/cell_controller';
+import { useEffect, useState, KeyboardEvent, useMemo } from 'react';
+
+export const EditCellText = ({ data, cellController }: { data: string; cellController: CellController<any, any> }) => {
+  const [value, setValue] = useState('');
+  const [contentRows, setContentRows] = useState(1);
+  useEffect(() => {
+    setValue(data);
+  }, [data]);
+
+  useEffect(() => {
+    setContentRows(Math.max(1, value.split('\n').length));
+  }, [value]);
+
+  const onTextFieldChange = async (v: string) => {
+    setValue(v);
+  };
+
+  const save = async () => {
+    await cellController?.saveCellData(value);
+  };
+
+  return (
+    <div>
+      <textarea
+        rows={contentRows}
+        value={value}
+        onChange={(e) => onTextFieldChange(e.target.value)}
+        onBlur={() => save()}
+      />
+    </div>
+  );
+};

+ 40 - 9
frontend/appflowy_tauri/src/appflowy_app/components/board/EditBoardRow/EditBoardCell.tsx → frontend/appflowy_tauri/src/appflowy_app/components/board/EditBoardRow/EditCellWrapper.tsx

@@ -2,7 +2,7 @@ import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
 import { useCell } from '$app/components/_shared/database-hooks/useCell';
 import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
 import { FieldController } from '$app/stores/effects/database/field/field_controller';
-import { FieldType, SelectOptionCellDataPB } from '@/services/backend';
+import { DateCellDataPB, FieldType, SelectOptionCellDataPB } from '@/services/backend';
 import { TextTypeSvg } from '$app/components/_shared/svg/TextTypeSvg';
 import { NumberTypeSvg } from '$app/components/_shared/svg/NumberTypeSvg';
 import { DateTypeSvg } from '$app/components/_shared/svg/DateTypeSvg';
@@ -11,8 +11,14 @@ import { MultiSelectTypeSvg } from '$app/components/_shared/svg/MultiSelectTypeS
 import { ChecklistTypeSvg } from '$app/components/_shared/svg/ChecklistTypeSvg';
 import { UrlTypeSvg } from '$app/components/_shared/svg/UrlTypeSvg';
 import { useAppSelector } from '$app/stores/store';
+import { getBgColor } from '$app/components/_shared/getColor';
+import { CheckboxSvg } from '$app/components/_shared/svg/CheckboxSvg';
+import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
+import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
+import { useState } from 'react';
+import { EditCellText } from '$app/components/board/EditBoardRow/EditCellText';
 
-export const EditBoardCell = ({
+export const EditCellWrapper = ({
   cellIdentifier,
   cellCache,
   fieldController,
@@ -21,11 +27,19 @@ export const EditBoardCell = ({
   cellCache: CellCache;
   fieldController: FieldController;
 }) => {
-  const { data } = useCell(cellIdentifier, cellCache, fieldController);
+  const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
   const databaseStore = useAppSelector((state) => state.database);
+  const [showEditField, setShowEditField] = useState(false);
+  const onEditFieldClick = () => {
+    setShowEditField(true);
+  };
+
   return (
     <div className={'flex w-full items-center'}>
-      <div className={'flex w-[180px] cursor-pointer items-center gap-2 rounded-lg px-4 py-2 hover:bg-shade-6'}>
+      <div
+        onClick={() => onEditFieldClick()}
+        className={'flex w-[180px] cursor-pointer items-center gap-2 rounded-lg px-4 py-2 hover:bg-shade-6'}
+      >
         <div className={'h-5 w-5 flex-shrink-0'}>
           {cellIdentifier.fieldType === FieldType.RichText && <TextTypeSvg></TextTypeSvg>}
           {cellIdentifier.fieldType === FieldType.Number && <NumberTypeSvg></NumberTypeSvg>}
@@ -34,20 +48,37 @@ export const EditBoardCell = ({
           {cellIdentifier.fieldType === FieldType.MultiSelect && <MultiSelectTypeSvg></MultiSelectTypeSvg>}
           {cellIdentifier.fieldType === FieldType.Checklist && <ChecklistTypeSvg></ChecklistTypeSvg>}
           {cellIdentifier.fieldType === FieldType.URL && <UrlTypeSvg></UrlTypeSvg>}
+          {cellIdentifier.fieldType === FieldType.Checkbox && <CheckboxSvg></CheckboxSvg>}
         </div>
         <span className={'overflow-hidden text-ellipsis whitespace-nowrap'}>
           {databaseStore.fields[cellIdentifier.fieldId].title}
         </span>
       </div>
-      <div className={'flex-1'}>
-        {(cellIdentifier.fieldType === FieldType.SingleSelect || cellIdentifier.fieldType === FieldType.MultiSelect) && (
-          <div>
+      <div className={'flex-1 cursor-pointer rounded-lg px-4 py-2 hover:bg-shade-6'}>
+        {(cellIdentifier.fieldType === FieldType.SingleSelect ||
+          cellIdentifier.fieldType === FieldType.MultiSelect ||
+          cellIdentifier.fieldType === FieldType.Checklist) && (
+          <div className={'flex items-center gap-2'}>
             {(data as SelectOptionCellDataPB | undefined)?.select_options?.map((option, index) => (
-              <div key={index}>{option?.name || ''}</div>
+              <div className={`${getBgColor(option.color)} rounded px-2 py-0.5 text-xs`} key={index}>
+                {option?.name || ''}
+              </div>
             )) || ''}
           </div>
         )}
-        {<div>{data as string}</div>}
+
+        {cellIdentifier.fieldType === FieldType.Checkbox && (
+          <div className={'h-8 w-8'}>
+            {(data as boolean | undefined) ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
+          </div>
+        )}
+
+        {cellIdentifier.fieldType === FieldType.DateTime && <div>{(data as DateCellDataPB | undefined)?.date}</div>}
+
+        {(cellIdentifier.fieldType === FieldType.RichText ||
+          cellIdentifier.fieldType === FieldType.URL ||
+          cellIdentifier.fieldType === FieldType.Number) &&
+          cellController && <EditCellText data={data as string} cellController={cellController}></EditCellText>}
       </div>
     </div>
   );

+ 19 - 7
frontend/appflowy_tauri/src/appflowy_app/components/board/EditBoardRow/EditBoardRow.tsx → frontend/appflowy_tauri/src/appflowy_app/components/board/EditBoardRow/EditRow.tsx

@@ -2,9 +2,10 @@ import { CloseSvg } from '$app/components/_shared/svg/CloseSvg';
 import { useRow } from '$app/components/_shared/database-hooks/useRow';
 import { DatabaseController } from '$app/stores/effects/database/database_controller';
 import { RowInfo } from '$app/stores/effects/database/row/row_cache';
-import { EditBoardCell } from '$app/components/board/EditBoardRow/EditBoardCell';
+import { EditCellWrapper } from '$app/components/board/EditBoardRow/EditCellWrapper';
+import AddSvg from '$app/components/_shared/svg/AddSvg';
 
-export const EditBoardRow = ({
+export const EditRow = ({
   onClose,
   viewId,
   controller,
@@ -15,26 +16,37 @@ export const EditBoardRow = ({
   controller: DatabaseController;
   rowInfo: RowInfo;
 }) => {
-  const { cells } = useRow(viewId, controller, rowInfo);
+  const { cells, onNewColumnClick } = useRow(viewId, controller, rowInfo);
 
   return (
     <div className={'fixed inset-0 z-20 flex items-center justify-center bg-black/30 backdrop-blur-sm'}>
-      <div className={'relative flex min-w-[70%] flex-col gap-8 rounded-xl bg-white p-8'}>
+      <div className={'relative flex min-h-[50%] min-w-[70%] flex-col gap-8 rounded-xl bg-white px-8 pb-4 pt-12'}>
         <div onClick={() => onClose()} className={'absolute top-4 right-4'}>
           <button className={'block h-8 w-8 rounded-lg text-shade-2 hover:bg-main-secondary'}>
             <CloseSvg></CloseSvg>
           </button>
         </div>
-        <div className={'flex flex-col gap-4'}>
+        <div className={'flex flex-1 flex-col gap-4'}>
           {cells.map((cell, cellIndex) => (
-            <EditBoardCell
+            <EditCellWrapper
               key={cellIndex}
               cellIdentifier={cell.cellIdentifier}
               cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
               fieldController={controller.fieldController}
-            ></EditBoardCell>
+            ></EditCellWrapper>
           ))}
         </div>
+        <div className={'border-t border-shade-6 pt-2'}>
+          <button
+            onClick={() => onNewColumnClick()}
+            className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-shade-6'}
+          >
+            <i className={'h-5 w-5'}>
+              <AddSvg></AddSvg>
+            </i>
+            <span>New Column</span>
+          </button>
+        </div>
       </div>
     </div>
   );