mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
wip: edit cell type
This commit is contained in:
parent
d05cc4005b
commit
da19987f4b
@ -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",
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>;
|
||||
};
|
@ -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 ||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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()}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user