|
@@ -2,45 +2,59 @@ import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell
|
|
|
import { DatabaseController } from '@/appflowy_app/stores/effects/database/database_controller';
|
|
|
import { TypeOptionController } from '@/appflowy_app/stores/effects/database/field/type_option/type_option_controller';
|
|
|
import { FieldType } from '@/services/backend';
|
|
|
-import { useState, useRef } from 'react';
|
|
|
+import { useState, useRef, useEffect } from 'react';
|
|
|
import { Some } from 'ts-results';
|
|
|
import { ChangeFieldTypePopup } from '../../_shared/EditRow/ChangeFieldTypePopup';
|
|
|
import { EditFieldPopup } from '../../_shared/EditRow/EditFieldPopup';
|
|
|
-import { ChecklistTypeSvg } from '../../_shared/svg/ChecklistTypeSvg';
|
|
|
-import { DateTypeSvg } from '../../_shared/svg/DateTypeSvg';
|
|
|
-import { MultiSelectTypeSvg } from '../../_shared/svg/MultiSelectTypeSvg';
|
|
|
-import { NumberTypeSvg } from '../../_shared/svg/NumberTypeSvg';
|
|
|
-import { SingleSelectTypeSvg } from '../../_shared/svg/SingleSelectTypeSvg';
|
|
|
-import { TextTypeSvg } from '../../_shared/svg/TextTypeSvg';
|
|
|
-import { UrlTypeSvg } from '../../_shared/svg/UrlTypeSvg';
|
|
|
+import { databaseActions, IDatabaseField } from '$app_reducers/database/slice';
|
|
|
+import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
|
|
|
+import { useResizer } from '$app/components/_shared/useResizer';
|
|
|
+import { useAppDispatch, useAppSelector } from '$app/stores/store';
|
|
|
+import { Details2Svg } from '$app/components/_shared/svg/Details2Svg';
|
|
|
+import { FilterSvg } from '$app/components/_shared/svg/FilterSvg';
|
|
|
+import { SortAscSvg } from '$app/components/_shared/svg/SortAscSvg';
|
|
|
+import { PromptWindow } from '$app/components/_shared/PromptWindow';
|
|
|
+
|
|
|
+const MIN_COLUMN_WIDTH = 100;
|
|
|
|
|
|
export const GridTableHeaderItem = ({
|
|
|
controller,
|
|
|
field,
|
|
|
+ index,
|
|
|
+ onShowFilterClick,
|
|
|
+ onShowSortClick,
|
|
|
}: {
|
|
|
controller: DatabaseController;
|
|
|
- field: {
|
|
|
- fieldId: string;
|
|
|
- name: string;
|
|
|
- fieldType: FieldType;
|
|
|
- };
|
|
|
+ field: IDatabaseField;
|
|
|
+ index: number;
|
|
|
+ onShowFilterClick: () => void;
|
|
|
+ onShowSortClick: () => void;
|
|
|
}) => {
|
|
|
- const [showFieldEditor, setShowFieldEditor] = useState(false);
|
|
|
- const [editFieldTop, setEditFieldTop] = useState(0);
|
|
|
- const [editFieldRight, setEditFieldRight] = useState(0);
|
|
|
+ const { onMouseDown, newSizeX } = useResizer((final) => {
|
|
|
+ if (final < MIN_COLUMN_WIDTH) return;
|
|
|
+ void controller.changeWidth({ fieldId: field.fieldId, width: final });
|
|
|
+ });
|
|
|
|
|
|
- const [showChangeFieldTypePopup, setShowChangeFieldTypePopup] = useState(false);
|
|
|
- const [changeFieldTypeTop, setChangeFieldTypeTop] = useState(0);
|
|
|
- const [changeFieldTypeRight, setChangeFieldTypeRight] = useState(0);
|
|
|
+ const filtersStore = useAppSelector((state) => state.database.filters);
|
|
|
+ const sortStore = useAppSelector((state) => state.database.sort);
|
|
|
|
|
|
- const [editingField, setEditingField] = useState<{
|
|
|
- fieldId: string;
|
|
|
- name: string;
|
|
|
- fieldType: FieldType;
|
|
|
- } | null>(null);
|
|
|
+ const dispatch = useAppDispatch();
|
|
|
+ const [showFieldEditor, setShowFieldEditor] = useState(false);
|
|
|
+ const [showChangeFieldTypePopup, setShowChangeFieldTypePopup] = useState(false);
|
|
|
+ const [changeFieldTypeAnchorEl, setChangeFieldTypeAnchorEl] = useState<HTMLDivElement | null>(null);
|
|
|
+ const [editingField, setEditingField] = useState<IDatabaseField | null>(null);
|
|
|
+ const [deletingPropertyId, setDeletingPropertyId] = useState<string | null>(null);
|
|
|
+ const [showDeletePropertyPrompt, setShowDeletePropertyPrompt] = useState(false);
|
|
|
|
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ if (!newSizeX) return;
|
|
|
+ if (newSizeX >= MIN_COLUMN_WIDTH) {
|
|
|
+ dispatch(databaseActions.changeWidth({ fieldId: field.fieldId, width: newSizeX }));
|
|
|
+ }
|
|
|
+ }, [newSizeX]);
|
|
|
+
|
|
|
const changeFieldType = async (newType: FieldType) => {
|
|
|
if (!editingField) return;
|
|
|
|
|
@@ -60,66 +74,115 @@ export const GridTableHeaderItem = ({
|
|
|
setShowChangeFieldTypePopup(false);
|
|
|
};
|
|
|
|
|
|
+ const onFieldOptionsClick = () => {
|
|
|
+ setEditingField(field);
|
|
|
+ setShowFieldEditor(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const onDeletePropertyClick = (fieldId: string) => {
|
|
|
+ setDeletingPropertyId(fieldId);
|
|
|
+ setShowDeletePropertyPrompt(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const onDelete = async () => {
|
|
|
+ if (!deletingPropertyId) return;
|
|
|
+ const fieldInfo = controller.fieldController.getField(deletingPropertyId);
|
|
|
+
|
|
|
+ if (!fieldInfo) return;
|
|
|
+ const typeController = new TypeOptionController(controller.viewId, Some(fieldInfo));
|
|
|
+
|
|
|
+ setEditingField(null);
|
|
|
+
|
|
|
+ await typeController.initialize();
|
|
|
+ await typeController.deleteField();
|
|
|
+ setShowDeletePropertyPrompt(false);
|
|
|
+ };
|
|
|
+
|
|
|
return (
|
|
|
- <th key={field.fieldId} className='m-0 border border-l-0 border-line-divider p-0'>
|
|
|
+ <>
|
|
|
<div
|
|
|
- className={'flex w-full cursor-pointer items-center px-4 py-2 hover:bg-fill-list-hover'}
|
|
|
- ref={ref}
|
|
|
- onClick={() => {
|
|
|
- if (!ref.current) return;
|
|
|
- const { top, left } = ref.current.getBoundingClientRect();
|
|
|
-
|
|
|
- setEditFieldRight(left - 10);
|
|
|
- setEditFieldTop(top + 35);
|
|
|
- setEditingField(field);
|
|
|
- setShowFieldEditor(true);
|
|
|
- }}
|
|
|
+ // field width minus divider width with padding
|
|
|
+ style={{ width: `${field.width - (index === 0 ? 7 : 14)}px` }}
|
|
|
+ className='flex-shrink-0 border-b border-t border-line-divider'
|
|
|
>
|
|
|
- <i className={'mr-2 h-5 w-5 text-text-caption'}>
|
|
|
- {field.fieldType === FieldType.RichText && <TextTypeSvg></TextTypeSvg>}
|
|
|
- {field.fieldType === FieldType.Number && <NumberTypeSvg></NumberTypeSvg>}
|
|
|
- {field.fieldType === FieldType.DateTime && <DateTypeSvg></DateTypeSvg>}
|
|
|
- {field.fieldType === FieldType.SingleSelect && <SingleSelectTypeSvg></SingleSelectTypeSvg>}
|
|
|
- {field.fieldType === FieldType.MultiSelect && <MultiSelectTypeSvg></MultiSelectTypeSvg>}
|
|
|
- {field.fieldType === FieldType.Checklist && <ChecklistTypeSvg></ChecklistTypeSvg>}
|
|
|
- {field.fieldType === FieldType.Checkbox && <ChecklistTypeSvg></ChecklistTypeSvg>}
|
|
|
- {field.fieldType === FieldType.URL && <UrlTypeSvg></UrlTypeSvg>}
|
|
|
- </i>
|
|
|
- <span>{field.name}</span>
|
|
|
-
|
|
|
- {showFieldEditor && editingField && (
|
|
|
- <EditFieldPopup
|
|
|
- top={editFieldTop}
|
|
|
- left={editFieldRight}
|
|
|
- cellIdentifier={
|
|
|
- {
|
|
|
- fieldId: editingField.fieldId,
|
|
|
- fieldType: editingField.fieldType,
|
|
|
- viewId: controller.viewId,
|
|
|
- } as CellIdentifier
|
|
|
- }
|
|
|
- viewId={controller.viewId}
|
|
|
- onOutsideClick={() => {
|
|
|
- setShowFieldEditor(false);
|
|
|
- }}
|
|
|
- fieldInfo={controller.fieldController.getField(editingField.fieldId)}
|
|
|
- changeFieldTypeClick={(buttonTop, buttonRight) => {
|
|
|
- setChangeFieldTypeTop(buttonTop);
|
|
|
- setChangeFieldTypeRight(buttonRight);
|
|
|
- setShowChangeFieldTypePopup(true);
|
|
|
- }}
|
|
|
- ></EditFieldPopup>
|
|
|
- )}
|
|
|
-
|
|
|
- {showChangeFieldTypePopup && (
|
|
|
- <ChangeFieldTypePopup
|
|
|
- top={changeFieldTypeTop}
|
|
|
- left={changeFieldTypeRight}
|
|
|
- onClick={(newType) => changeFieldType(newType)}
|
|
|
- onOutsideClick={() => setShowChangeFieldTypePopup(false)}
|
|
|
- ></ChangeFieldTypePopup>
|
|
|
- )}
|
|
|
+ <div className={'flex w-full items-center justify-between py-2 pl-2'} ref={ref}>
|
|
|
+ <div className={'flex min-w-0 items-center gap-2'}>
|
|
|
+ <div className={'flex h-5 w-5 flex-shrink-0 items-center justify-center text-text-caption'}>
|
|
|
+ <FieldTypeIcon fieldType={field.fieldType}></FieldTypeIcon>
|
|
|
+ </div>
|
|
|
+ <span className={'overflow-hidden text-ellipsis whitespace-nowrap text-text-caption'}>{field.title}</span>
|
|
|
+ </div>
|
|
|
+ <div className={'flex items-center gap-1'}>
|
|
|
+ {sortStore.findIndex((sort) => sort.fieldId === field.fieldId) !== -1 && (
|
|
|
+ <button onClick={onShowSortClick} className={'rounded p-1 hover:bg-fill-list-hover'}>
|
|
|
+ <i className={'block h-[16px] w-[16px]'}>
|
|
|
+ <SortAscSvg></SortAscSvg>
|
|
|
+ </i>
|
|
|
+ </button>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {filtersStore.findIndex((filter) => filter.fieldId === field.fieldId) !== -1 && (
|
|
|
+ <button onClick={onShowFilterClick} className={'rounded p-1 hover:bg-fill-list-hover'}>
|
|
|
+ <i className={'block h-[16px] w-[16px]'}>
|
|
|
+ <FilterSvg></FilterSvg>
|
|
|
+ </i>
|
|
|
+ </button>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <button className={'rounded p-1 hover:bg-fill-list-hover'} onClick={() => onFieldOptionsClick()}>
|
|
|
+ <i className={'block h-[16px] w-[16px]'}>
|
|
|
+ <Details2Svg></Details2Svg>
|
|
|
+ </i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </th>
|
|
|
+ <div
|
|
|
+ className={'group h-full cursor-col-resize border-b border-t border-line-divider px-[6px]'}
|
|
|
+ onMouseDown={(e) => onMouseDown(e, field.width)}
|
|
|
+ >
|
|
|
+ <div className={'flex h-full w-[3px] justify-center group-hover:bg-fill-hover'}>
|
|
|
+ <div className={'h-full w-[1px] bg-line-divider group-hover:bg-fill-hover'}></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {editingField && (
|
|
|
+ <EditFieldPopup
|
|
|
+ open={showFieldEditor}
|
|
|
+ anchorEl={ref.current}
|
|
|
+ cellIdentifier={
|
|
|
+ {
|
|
|
+ fieldId: editingField.fieldId,
|
|
|
+ fieldType: editingField.fieldType,
|
|
|
+ viewId: controller.viewId,
|
|
|
+ } as CellIdentifier
|
|
|
+ }
|
|
|
+ viewId={controller.viewId}
|
|
|
+ onOutsideClick={() => {
|
|
|
+ setShowFieldEditor(false);
|
|
|
+ }}
|
|
|
+ controller={controller}
|
|
|
+ changeFieldTypeClick={(el) => {
|
|
|
+ setChangeFieldTypeAnchorEl(el);
|
|
|
+ setShowChangeFieldTypePopup(true);
|
|
|
+ }}
|
|
|
+ onDeletePropertyClick={onDeletePropertyClick}
|
|
|
+ ></EditFieldPopup>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <ChangeFieldTypePopup
|
|
|
+ open={showChangeFieldTypePopup}
|
|
|
+ anchorEl={changeFieldTypeAnchorEl}
|
|
|
+ onClick={(newType) => changeFieldType(newType)}
|
|
|
+ onOutsideClick={() => setShowChangeFieldTypePopup(false)}
|
|
|
+ ></ChangeFieldTypePopup>
|
|
|
+
|
|
|
+ {showDeletePropertyPrompt && (
|
|
|
+ <PromptWindow
|
|
|
+ msg={'Are you sure you want to delete this property?'}
|
|
|
+ onYes={() => onDelete()}
|
|
|
+ onCancel={() => setShowDeletePropertyPrompt(false)}
|
|
|
+ ></PromptWindow>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
);
|
|
|
};
|