Pārlūkot izejas kodu

wip: edit cell type

ascarbek 2 gadi atpakaļ
vecāks
revīzija
da19987f4b

+ 1 - 0
frontend/appflowy_tauri/package.json

@@ -34,6 +34,7 @@
     "react-i18next": "^12.2.0",
     "react-redux": "^8.0.5",
     "react-router-dom": "^6.8.0",
+    "react-tailwindcss-datepicker": "^1.5.1",
     "react18-input-otp": "^1.1.2",
     "redux": "^4.2.1",
     "rxjs": "^7.8.0",

+ 55 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/ChangeFieldTypePopup.tsx

@@ -0,0 +1,55 @@
+import { FieldType } from '@/services/backend';
+import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
+import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
+import { useEffect, useRef, useState } from 'react';
+
+const typesOrder: FieldType[] = [
+  FieldType.RichText,
+  FieldType.Number,
+  FieldType.DateTime,
+  FieldType.SingleSelect,
+  FieldType.MultiSelect,
+  FieldType.Checkbox,
+  FieldType.URL,
+  FieldType.Checklist,
+];
+
+export const ChangeFieldTypePopup = ({ top, right, onClick }: { top: number; right: number; onClick: () => void }) => {
+  const ref = useRef<HTMLDivElement>(null);
+  const [adjustedTop, setAdjustedTop] = useState(0);
+
+  useEffect(() => {
+    if (!ref.current) return;
+    const { height } = ref.current.getBoundingClientRect();
+    if (top + height > window.innerHeight) {
+      setAdjustedTop(window.innerHeight - height);
+    } else {
+      setAdjustedTop(top);
+    }
+  }, [ref, window, top, right]);
+
+  return (
+    <div
+      ref={ref}
+      className={'fixed z-20 rounded-lg bg-white p-2 shadow-md'}
+      style={{ top: `${adjustedTop}px`, left: `${right + 30}px` }}
+    >
+      <div className={'flex flex-col'}>
+        {typesOrder.map((t, i) => (
+          <button
+            onClick={() => onClick()}
+            key={i}
+            className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
+          >
+            <i className={'h-5 w-5'}>
+              <FieldTypeIcon fieldType={t}></FieldTypeIcon>
+            </i>
+            <span>
+              <FieldTypeName fieldType={t}></FieldTypeName>
+            </span>
+          </button>
+        ))}
+      </div>
+    </div>
+  );
+};

+ 24 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellDate.tsx

@@ -0,0 +1,24 @@
+import Picker from 'react-tailwindcss-datepicker';
+import { DateValueType } from 'react-tailwindcss-datepicker/dist/types';
+import { useState } from 'react';
+import { DateCellDataPB } from '@/services/backend';
+import { CellController } from '$app/stores/effects/database/cell/cell_controller';
+
+export const EditCellDate = ({
+  data,
+  cellController,
+}: {
+  data?: DateCellDataPB;
+  cellController: CellController<any, any>;
+}) => {
+  const [value, setValue] = useState<DateValueType>({
+    startDate: new Date(),
+    endDate: new Date(),
+  });
+
+  const onChange = (v: DateValueType) => {
+    console.log(v);
+  };
+
+  return <Picker value={value} onChange={onChange} useRange={false} asSingle={true}></Picker>;
+};

+ 15 - 19
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellWrapper.tsx

@@ -7,33 +7,37 @@ import { useAppSelector } from '$app/stores/store';
 import { getBgColor } from '$app/components/_shared/getColor';
 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/_shared/EditRow/EditCellText';
-import { EditFieldPopup } from '$app/components/_shared/EditRow/EditFieldPopup';
 import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
+import { EditCellDate } from '$app/components/_shared/EditRow/EditCellDate';
+import { useRef } from 'react';
 
 export const EditCellWrapper = ({
-  viewId,
   cellIdentifier,
   cellCache,
   fieldController,
+  onEditFieldClick,
 }: {
-  viewId: string;
   cellIdentifier: CellIdentifier;
   cellCache: CellCache;
   fieldController: FieldController;
+  onEditFieldClick: (top: number, right: number) => void;
 }) => {
   const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
   const databaseStore = useAppSelector((state) => state.database);
-  const [showFieldEditor, setShowFieldEditor] = useState(false);
-  const onEditFieldClick = () => {
-    setShowFieldEditor(true);
+  const el = useRef<HTMLDivElement>(null);
+
+  const onClick = () => {
+    if (!el.current) return;
+    const { top, right } = el.current.getBoundingClientRect();
+    onEditFieldClick(top, right);
   };
 
   return (
     <div className={'flex w-full items-center text-xs'}>
       <div
-        onClick={() => onEditFieldClick()}
+        ref={el}
+        onClick={() => onClick()}
         className={'relative flex w-[180px] cursor-pointer items-center gap-2 rounded-lg px-3 py-1.5 hover:bg-shade-6'}
       >
         <div className={'flex h-5 w-5 flex-shrink-0 items-center justify-center'}>
@@ -42,16 +46,6 @@ export const EditCellWrapper = ({
         <span className={'overflow-hidden text-ellipsis whitespace-nowrap'}>
           {databaseStore.fields[cellIdentifier.fieldId].title}
         </span>
-        {showFieldEditor && cellController && (
-          <EditFieldPopup
-            fieldName={databaseStore.fields[cellIdentifier.fieldId].title}
-            fieldType={cellIdentifier.fieldType}
-            viewId={viewId}
-            cellController={cellController}
-            onOutsideClick={() => setShowFieldEditor(false)}
-            fieldInfo={fieldController.getField(cellIdentifier.fieldId)}
-          ></EditFieldPopup>
-        )}
       </div>
       <div className={'flex-1 cursor-pointer rounded-lg px-4 py-2 hover:bg-shade-6'}>
         {(cellIdentifier.fieldType === FieldType.SingleSelect ||
@@ -72,7 +66,9 @@ export const EditCellWrapper = ({
           </div>
         )}
 
-        {cellIdentifier.fieldType === FieldType.DateTime && <div>{(data as DateCellDataPB | undefined)?.date}</div>}
+        {cellIdentifier.fieldType === FieldType.DateTime && cellController && (
+          <EditCellDate data={data as DateCellDataPB | undefined} cellController={cellController}></EditCellDate>
+        )}
 
         {(cellIdentifier.fieldType === FieldType.RichText ||
           cellIdentifier.fieldType === FieldType.URL ||

+ 63 - 21
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx

@@ -1,41 +1,60 @@
 import { useEffect, useRef, useState } from 'react';
 import useOutsideClick from '$app/components/_shared/useOutsideClick';
 import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
-import { CellController } from '$app/stores/effects/database/cell/cell_controller';
-import { FieldType } from '@/services/backend';
 import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
 import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
 import { useTranslation } from 'react-i18next';
 import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
 import { Some } from 'ts-results';
 import { FieldInfo } from '$app/stores/effects/database/field/field_controller';
+import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
+import { ChangeFieldTypePopup } from '$app/components/_shared/EditRow/ChangeFieldTypePopup';
+import { useAppSelector } from '$app/stores/store';
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
 
 export const EditFieldPopup = ({
+  top,
+  right,
+  cellIdentifier,
   viewId,
-  fieldName,
   onOutsideClick,
-  fieldType,
-  cellController,
   fieldInfo,
+                                 // changeFieldTypeClick,
 }: {
+  top: number;
+  right: number;
+  cellIdentifier: CellIdentifier;
   viewId: string;
-  fieldName: string;
   onOutsideClick?: () => void;
-  fieldType: FieldType;
-  cellController: CellController<any, any>;
   fieldInfo: FieldInfo | undefined;
+  // changeFieldTypeClick: (top: number, right: number) => void
 }) => {
+  const databaseStore = useAppSelector((state) => state.database);
   const { t } = useTranslation('');
   const ref = useRef<HTMLDivElement>(null);
+  const changeTypeButtonRef = useRef<HTMLDivElement>(null);
   const [name, setName] = useState('');
+
+  const [adjustedTop, setAdjustedTop] = useState(0);
+
   useOutsideClick(ref, async () => {
     await save();
     onOutsideClick && onOutsideClick();
   });
 
   useEffect(() => {
-    setName(fieldName);
-  }, [fieldName]);
+    setName(databaseStore.fields[cellIdentifier.fieldId].title);
+  }, [databaseStore, cellIdentifier]);
+
+  useEffect(() => {
+    if (!ref.current) return;
+    const { height } = ref.current.getBoundingClientRect();
+    if (top + height > window.innerHeight) {
+      setAdjustedTop(window.innerHeight - height);
+    } else {
+      setAdjustedTop(top);
+    }
+  }, [ref, window, top, right]);
 
   const save = async () => {
     if (!fieldInfo) return;
@@ -44,36 +63,59 @@ export const EditFieldPopup = ({
     await controller.setFieldName(name);
   };
 
+  const onChangeFieldTypeClick = () => {
+    if (!changeTypeButtonRef.current) return;
+    const { top: newTop, right: newRight } = changeTypeButtonRef.current.getBoundingClientRect();
+    // setChangeFieldTypeTop(newTop);
+    // setChangeFieldTypeRight(newRight);
+    // setShowChangeFieldTypePopup(true);
+    // changeFieldTypeClick(newTop, newRight);
+  };
+
   return (
-    <div ref={ref} className={`absolute left-full top-0 rounded-lg bg-white px-2 py-2 shadow-md`}>
-      <div className={'flex flex-col gap-4 p-4'}>
+    <div
+      ref={ref}
+      className={`fixed z-20 rounded-lg bg-white px-2 py-2 text-xs shadow-md`}
+      style={{ top: `${adjustedTop}px`, left: `${right + 10}px` }}
+    >
+      <div className={'flex flex-col gap-2 p-2'}>
         <input
           value={name}
           onChange={(e) => setName(e.target.value)}
           onBlur={() => save()}
-          className={'border-shades-3 flex-1 rounded border bg-main-selector p-1'}
+          className={'border-shades-3 flex-1 rounded border bg-main-selector px-2 py-2'}
         />
+
         <button
           className={
             'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 text-main-alert hover:bg-main-secondary'
           }
         >
-          <i className={'mb-0.5 h-5 w-5'}>
+          <i className={'h-5 w-5'}>
             <TrashSvg></TrashSvg>
           </i>
           <span>{t('grid.field.delete')}</span>
         </button>
 
-        <button
-          className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 text-black hover:bg-main-secondary'}
+        <div
+          ref={changeTypeButtonRef}
+          onClick={() => onChangeFieldTypeClick()}
+          className={
+            'relative flex cursor-pointer items-center justify-between rounded-lg text-black hover:bg-main-secondary'
+          }
         >
+          <button className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2'}>
+            <i className={'h-5 w-5'}>
+              <FieldTypeIcon fieldType={cellIdentifier.fieldType}></FieldTypeIcon>
+            </i>
+            <span>
+              <FieldTypeName fieldType={cellIdentifier.fieldType}></FieldTypeName>
+            </span>
+          </button>
           <i className={'h-5 w-5'}>
-            <FieldTypeIcon fieldType={fieldType}></FieldTypeIcon>
+            <MoreSvg></MoreSvg>
           </i>
-          <span>
-            <FieldTypeName fieldType={fieldType}></FieldTypeName>
-          </span>
-        </button>
+        </div>
       </div>
     </div>
   );

+ 39 - 3
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx

@@ -5,6 +5,10 @@ import { RowInfo } from '$app/stores/effects/database/row/row_cache';
 import { EditCellWrapper } from '$app/components/_shared/EditRow/EditCellWrapper';
 import AddSvg from '$app/components/_shared/svg/AddSvg';
 import { useTranslation } from 'react-i18next';
+import { EditFieldPopup } from '$app/components/_shared/EditRow/EditFieldPopup';
+import { useState } from 'react';
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+import { ChangeFieldTypePopup } from '$app/components/_shared/EditRow/ChangeFieldTypePopup';
 
 export const EditRow = ({
   onClose,
@@ -19,26 +23,58 @@ export const EditRow = ({
 }) => {
   const { cells, onNewColumnClick } = useRow(viewId, controller, rowInfo);
   const { t } = useTranslation('');
+  const [showFieldEditor, setShowFieldEditor] = useState(false);
+  const [editFieldTop, setEditFieldTop] = useState(0);
+  const [editFieldRight, setEditFieldRight] = useState(0);
+  const [showChangeFieldTypePopup, setShowChangeFieldTypePopup] = useState(false);
+  const [changeFieldTypeTop, setChangeFieldTypeTop] = useState(0);
+  const [changeFieldTypeRight, setChangeFieldTypeRight] = useState(0);
+
+  const onEditFieldClick = (cell: { cellIdentifier: CellIdentifier; fieldId: string }, top: number, right: number) => {
+    setEditingCell(cell);
+    setEditFieldTop(top);
+    setEditFieldRight(right);
+    setShowFieldEditor(true);
+  };
+
+  const [editingCell, setEditingCell] = useState<{ cellIdentifier: CellIdentifier; fieldId: string } | null>(null);
 
   return (
     <div className={'fixed inset-0 z-20 flex items-center justify-center bg-black/30 backdrop-blur-sm'}>
-      <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 className={'flex h-[90%] 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-1 flex-col gap-2'}>
+        <div className={`flex flex-1 flex-col gap-2 ${showFieldEditor ? 'overflow-hidden' : 'overflow-auto'}`}>
           {cells.map((cell, cellIndex) => (
             <EditCellWrapper
               key={cellIndex}
-              viewId={viewId}
               cellIdentifier={cell.cellIdentifier}
               cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
               fieldController={controller.fieldController}
+              onEditFieldClick={(top: number, right: number) => onEditFieldClick(cell, top, right)}
             ></EditCellWrapper>
           ))}
         </div>
+        {showFieldEditor && editingCell && (
+          <EditFieldPopup
+            top={editFieldTop}
+            right={editFieldRight}
+            cellIdentifier={editingCell.cellIdentifier}
+            viewId={viewId}
+            onOutsideClick={() => setShowFieldEditor(false)}
+            fieldInfo={controller.fieldController.getField(editingCell.cellIdentifier.fieldId)}
+          ></EditFieldPopup>
+        )}
+        {showChangeFieldTypePopup && (
+          <ChangeFieldTypePopup
+            top={changeFieldTypeTop}
+            right={changeFieldTypeRight}
+            onClick={() => setShowChangeFieldTypePopup(false)}
+          ></ChangeFieldTypePopup>
+        )}
         <div className={'border-t border-shade-6 pt-2'}>
           <button
             onClick={() => onNewColumnClick()}

+ 10 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/MoreSvg.tsx

@@ -0,0 +1,10 @@
+export const MoreSvg = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <path
+        d='M9.39568 7.6963L6.91032 5.56599C6.65085 5.34358 6.25 5.52795 6.25 5.86969L6.25 10.1303C6.25 10.4721 6.65085 10.6564 6.91032 10.434L9.39568 8.3037C9.58192 8.14406 9.58192 7.85594 9.39568 7.6963Z'
+        fill='currentColor'
+      />
+    </svg>
+  );
+};