mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Fix/tauri warning to error (#3869)
* feat: sort basic function * fix: eslint error * fix: deal with conflict * fix: prevent submit eslint warning code * fix: modify tauri warning to error --------- Co-authored-by: fangwufeng-v <fangwufeng.v@gmail.com>
This commit is contained in:
parent
73ea1a0685
commit
5f49c1748f
@ -15,20 +15,20 @@ module.exports = {
|
|||||||
plugins: ['@typescript-eslint', "react-hooks"],
|
plugins: ['@typescript-eslint', "react-hooks"],
|
||||||
rules: {
|
rules: {
|
||||||
"react-hooks/rules-of-hooks": "error",
|
"react-hooks/rules-of-hooks": "error",
|
||||||
"react-hooks/exhaustive-deps": "warn",
|
"react-hooks/exhaustive-deps": "error",
|
||||||
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||||
'@typescript-eslint/no-empty-function': 'error',
|
'@typescript-eslint/no-empty-function': 'error',
|
||||||
'@typescript-eslint/no-empty-interface': 'warn',
|
'@typescript-eslint/no-empty-interface': 'error',
|
||||||
'@typescript-eslint/no-floating-promises': 'warn',
|
'@typescript-eslint/no-floating-promises': 'error',
|
||||||
'@typescript-eslint/await-thenable': 'error',
|
'@typescript-eslint/await-thenable': 'error',
|
||||||
'@typescript-eslint/no-namespace': 'error',
|
'@typescript-eslint/no-namespace': 'error',
|
||||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||||
'@typescript-eslint/no-redeclare': 'error',
|
'@typescript-eslint/no-redeclare': 'error',
|
||||||
'@typescript-eslint/prefer-for-of': 'warn',
|
'@typescript-eslint/prefer-for-of': 'error',
|
||||||
'@typescript-eslint/triple-slash-reference': 'error',
|
'@typescript-eslint/triple-slash-reference': 'error',
|
||||||
'@typescript-eslint/unified-signatures': 'warn',
|
'@typescript-eslint/unified-signatures': 'error',
|
||||||
'no-shadow': 'off',
|
'no-shadow': 'off',
|
||||||
'@typescript-eslint/no-shadow': 'warn',
|
'@typescript-eslint/no-shadow': 'off',
|
||||||
'constructor-super': 'error',
|
'constructor-super': 'error',
|
||||||
eqeqeq: ['error', 'always'],
|
eqeqeq: ['error', 'always'],
|
||||||
'no-cond-assign': 'error',
|
'no-cond-assign': 'error',
|
||||||
@ -47,18 +47,18 @@ module.exports = {
|
|||||||
'no-throw-literal': 'error',
|
'no-throw-literal': 'error',
|
||||||
'no-unsafe-finally': 'error',
|
'no-unsafe-finally': 'error',
|
||||||
'no-unused-labels': 'error',
|
'no-unused-labels': 'error',
|
||||||
'no-var': 'warn',
|
'no-var': 'error',
|
||||||
'no-void': 'off',
|
'no-void': 'off',
|
||||||
'prefer-const': 'warn',
|
'prefer-const': 'error',
|
||||||
'prefer-spread': 'off',
|
'prefer-spread': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'warn',
|
'error',
|
||||||
{
|
{
|
||||||
argsIgnorePattern: '^_',
|
argsIgnorePattern: '^_',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'padding-line-between-statements': [
|
'padding-line-between-statements': [
|
||||||
"warn",
|
"error",
|
||||||
{ blankLine: "always", prev: ["const", "let", "var"], next: "*"},
|
{ blankLine: "always", prev: ["const", "let", "var"], next: "*"},
|
||||||
{ blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]},
|
{ blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]},
|
||||||
{ blankLine: "always", prev: "import", next: "*" },
|
{ blankLine: "always", prev: "import", next: "*" },
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
||||||
"test:errors": "pnpm sync:i18n && tsc --noEmit && eslint --quiet --ext .js,.ts,.tsx .",
|
"test:errors": "pnpm sync:i18n && tsc --noEmit && eslint --ext .js,.ts,.tsx .",
|
||||||
"test:prettier": "pnpm prettier --list-different src",
|
"test:prettier": "pnpm prettier --list-different src",
|
||||||
"tauri:clean": "cargo make --cwd .. tauri_clean",
|
"tauri:clean": "cargo make --cwd .. tauri_clean",
|
||||||
"tauri:dev": "pnpm sync:i18n && tauri dev",
|
"tauri:dev": "pnpm sync:i18n && tauri dev",
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="15.7124" y="7.22656" width="1.5" height="12" rx="0.75" transform="rotate(45 15.7124 7.22656)" fill="#333333"/>
|
||||||
|
<rect x="16.7729" y="15.7109" width="1.5" height="12" rx="0.75" transform="rotate(135 16.7729 15.7109)" fill="#333333"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 344 B |
@ -66,7 +66,7 @@ function BlockDragDropContext({ children }: { children: React.ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onDragEnd = () => {
|
const onDragEnd = () => {
|
||||||
dispatch(onDragEndThunk());
|
void dispatch(onDragEndThunk());
|
||||||
unlisten();
|
unlisten();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ function BlockDraggable(
|
|||||||
} & HTMLAttributes<HTMLDivElement>,
|
} & HTMLAttributes<HTMLDivElement>,
|
||||||
ref: React.Ref<HTMLDivElement>
|
ref: React.Ref<HTMLDivElement>
|
||||||
) {
|
) {
|
||||||
const { onDragStart, beforeDropping, afterDropping, childDropping, isDragging } = useDraggableState(id, type);
|
const { onDragStart, beforeDropping, afterDropping, childDropping } = useDraggableState(id, type);
|
||||||
|
|
||||||
const commonCls = 'pointer-events-none absolute z-10 w-[100%] bg-fill-hover transition-all duration-200';
|
const commonCls = 'pointer-events-none absolute z-10 w-[100%] bg-fill-hover transition-all duration-200';
|
||||||
|
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { useAppDispatch, useAppSelector } from '../../stores/store';
|
import { useAppSelector } from '$app/stores/store';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { databaseActions, IDatabase } from '../../stores/reducers/database/slice';
|
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { FieldType } from '../../../services/backend';
|
|
||||||
|
|
||||||
export const useDatabase = () => {
|
export const useDatabase = () => {
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const database = useAppSelector((state) => state.database);
|
const database = useAppSelector((state) => state.database);
|
||||||
|
|
||||||
const newField = () => {
|
const newField = () => {
|
||||||
@ -22,7 +17,7 @@ export const useDatabase = () => {
|
|||||||
console.log('depreciated');
|
console.log('depreciated');
|
||||||
};
|
};
|
||||||
|
|
||||||
const renameField = (fieldId: string, newTitle: string) => {
|
const renameField = (_fieldId: string, _newTitle: string) => {
|
||||||
/* const field = database.fields[fieldId];
|
/* const field = database.fields[fieldId];
|
||||||
field.title = newTitle;
|
field.title = newTitle;
|
||||||
|
|
||||||
|
@ -75,6 +75,7 @@ export const DatabaseFilterItem = ({
|
|||||||
value: currentValue,
|
value: currentValue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentFieldId, currentFieldType, currentOperator, currentValue, textInputActive]);
|
}, [currentFieldId, currentFieldType, currentOperator, currentValue, textInputActive]);
|
||||||
|
|
||||||
// 1. not all field types support filtering
|
// 1. not all field types support filtering
|
||||||
|
@ -54,6 +54,7 @@ export const DatabaseSortItem = ({
|
|||||||
fieldType: fields[currentFieldId].fieldType,
|
fieldType: fields[currentFieldId].fieldType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentFieldId, currentOrder]);
|
}, [currentFieldId, currentOrder]);
|
||||||
|
|
||||||
const onSelectFieldClick = (id: string) => {
|
const onSelectFieldClick = (id: string) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { MouseEventHandler, useMemo, useRef, useState } from 'react';
|
import { MouseEventHandler, useMemo, useState } from 'react';
|
||||||
import { useAppSelector } from '$app/stores/store';
|
import { useAppSelector } from '$app/stores/store';
|
||||||
import { IDatabaseSort } from '$app_reducers/database/slice';
|
import { IDatabaseSort } from '$app_reducers/database/slice';
|
||||||
import { DatabaseSortItem } from '$app/components/_shared/DatabaseSort/DatabaseSortItem';
|
import { DatabaseSortItem } from '$app/components/_shared/DatabaseSort/DatabaseSortItem';
|
||||||
|
@ -19,6 +19,7 @@ export const NewCheckListOption = ({
|
|||||||
|
|
||||||
const updateNewOption = (value: string) => {
|
const updateNewOption = (value: string) => {
|
||||||
const newOptionsCopy = [...newOptions];
|
const newOptionsCopy = [...newOptions];
|
||||||
|
|
||||||
newOptionsCopy[index] = value;
|
newOptionsCopy[index] = value;
|
||||||
setNewOptions(newOptionsCopy);
|
setNewOptions(newOptionsCopy);
|
||||||
};
|
};
|
||||||
|
@ -29,6 +29,7 @@ export const DateFormatPopup = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDateType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as IDateType);
|
setDateType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as IDateType);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [databaseStore]);
|
}, [databaseStore]);
|
||||||
|
|
||||||
const changeFormat = async (format: DateFormatPB) => {
|
const changeFormat = async (format: DateFormatPB) => {
|
||||||
|
@ -30,6 +30,7 @@ export const DatePickerPopup = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const date_pb = data as DateCellDataPB | undefined;
|
const date_pb = data as DateCellDataPB | undefined;
|
||||||
|
|
||||||
if (!date_pb || !date_pb?.date.length) return;
|
if (!date_pb || !date_pb?.date.length) return;
|
||||||
|
|
||||||
setSelectedDate(dayjs(date_pb.date).toDate());
|
setSelectedDate(dayjs(date_pb.date).toDate());
|
||||||
@ -39,6 +40,7 @@ export const DatePickerPopup = ({
|
|||||||
if (v instanceof Date) {
|
if (v instanceof Date) {
|
||||||
setSelectedDate(v);
|
setSelectedDate(v);
|
||||||
const date = new CalendarData(dayjs(v).add(dayjs().utcOffset(), 'minutes').toDate(), false);
|
const date = new CalendarData(dayjs(v).add(dayjs().utcOffset(), 'minutes').toDate(), false);
|
||||||
|
|
||||||
await cellController?.saveCellData(date);
|
await cellController?.saveCellData(date);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -8,11 +8,14 @@ import { FieldController } from '$app/stores/effects/database/field/field_contro
|
|||||||
export const useDateTimeFormat = (cellIdentifier: CellIdentifier, fieldController: FieldController) => {
|
export const useDateTimeFormat = (cellIdentifier: CellIdentifier, fieldController: FieldController) => {
|
||||||
const changeFormat = async (change: (option: DateTypeOptionPB) => void) => {
|
const changeFormat = async (change: (option: DateTypeOptionPB) => void) => {
|
||||||
const fieldInfo = fieldController.getField(cellIdentifier.fieldId);
|
const fieldInfo = fieldController.getField(cellIdentifier.fieldId);
|
||||||
|
|
||||||
if (!fieldInfo) return;
|
if (!fieldInfo) return;
|
||||||
const typeOptionController = new TypeOptionController(cellIdentifier.viewId, Some(fieldInfo), FieldType.DateTime);
|
const typeOptionController = new TypeOptionController(cellIdentifier.viewId, Some(fieldInfo), FieldType.DateTime);
|
||||||
|
|
||||||
await typeOptionController.initialize();
|
await typeOptionController.initialize();
|
||||||
const dateTypeOptionContext = makeDateTypeOptionContext(typeOptionController);
|
const dateTypeOptionContext = makeDateTypeOptionContext(typeOptionController);
|
||||||
const typeOption = await dateTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
const typeOption = await dateTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
||||||
|
|
||||||
change(typeOption);
|
change(typeOption);
|
||||||
await dateTypeOptionContext.setTypeOption(typeOption);
|
await dateTypeOptionContext.setTypeOption(typeOption);
|
||||||
};
|
};
|
||||||
@ -20,11 +23,13 @@ export const useDateTimeFormat = (cellIdentifier: CellIdentifier, fieldControlle
|
|||||||
const changeDateFormat = async (format: DateFormatPB) => {
|
const changeDateFormat = async (format: DateFormatPB) => {
|
||||||
await changeFormat((option) => (option.date_format = format));
|
await changeFormat((option) => (option.date_format = format));
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeTimeFormat = async (format: TimeFormatPB) => {
|
const changeTimeFormat = async (format: TimeFormatPB) => {
|
||||||
await changeFormat((option) => (option.time_format = format));
|
await changeFormat((option) => (option.time_format = format));
|
||||||
};
|
};
|
||||||
const includeTime = async (include: boolean) => {
|
|
||||||
await changeFormat((option) => {
|
const includeTime = async (_include: boolean) => {
|
||||||
|
await changeFormat((_option) => {
|
||||||
// option.include_time = include;
|
// option.include_time = include;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { DateFormatPopup } from '$app/components/_shared/EditRow/Date/DateFormatPopup';
|
import { DateFormatPopup } from '$app/components/_shared/EditRow/Date/DateFormatPopup';
|
||||||
import { TimeFormatPopup } from '$app/components/_shared/EditRow/Date/TimeFormatPopup';
|
import { TimeFormatPopup } from '$app/components/_shared/EditRow/Date/TimeFormatPopup';
|
||||||
import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
|
import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
|
||||||
import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
|
|
||||||
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
|
|
||||||
import { MouseEventHandler, useEffect, useState } from 'react';
|
import { MouseEventHandler, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IDateType } from '$app_reducers/database/slice';
|
import { IDateType } from '$app_reducers/database/slice';
|
||||||
@ -27,14 +25,16 @@ export const DateTypeOptions = ({
|
|||||||
const [showTimeFormatPopup, setShowTimeFormatPopup] = useState(false);
|
const [showTimeFormatPopup, setShowTimeFormatPopup] = useState(false);
|
||||||
const [timeFormatTop, setTimeFormatTop] = useState(0);
|
const [timeFormatTop, setTimeFormatTop] = useState(0);
|
||||||
const [timeFormatLeft, setTimeFormatLeft] = useState(0);
|
const [timeFormatLeft, setTimeFormatLeft] = useState(0);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [dateType, setDateType] = useState<IDateType | undefined>();
|
const [dateType, setDateType] = useState<IDateType | undefined>();
|
||||||
|
|
||||||
const databaseStore = useAppSelector((state) => state.database);
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { includeTime } = useDateTimeFormat(cellIdentifier, fieldController);
|
const { includeTime } = useDateTimeFormat(cellIdentifier, fieldController);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDateType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as IDateType);
|
setDateType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as IDateType);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [databaseStore]);
|
}, [databaseStore]);
|
||||||
|
|
||||||
const onDateFormatClick = (_left: number, _top: number) => {
|
const onDateFormatClick = (_left: number, _top: number) => {
|
||||||
@ -87,7 +87,7 @@ export const DateTypeOptions = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex flex-col'}>
|
<div className={'flex flex-col'}>
|
||||||
<hr className={'border-line-divider -mx-2 my-2'} />
|
<hr className={'-mx-2 my-2 border-line-divider'} />
|
||||||
<button
|
<button
|
||||||
onClick={_onDateFormatClick}
|
onClick={_onDateFormatClick}
|
||||||
className={
|
className={
|
||||||
|
@ -13,6 +13,7 @@ export const EditCellDate = ({
|
|||||||
const onClick: MouseEventHandler = () => {
|
const onClick: MouseEventHandler = () => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
const { left, top } = ref.current.getBoundingClientRect();
|
const { left, top } = ref.current.getBoundingClientRect();
|
||||||
|
|
||||||
onEditClick(left, top);
|
onEditClick(left, top);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,11 +8,14 @@ import { makeNumberTypeOptionContext } from '$app/stores/effects/database/field/
|
|||||||
export const useNumberFormat = (cellIdentifier: CellIdentifier, fieldController: FieldController) => {
|
export const useNumberFormat = (cellIdentifier: CellIdentifier, fieldController: FieldController) => {
|
||||||
const changeNumberFormat = async (format: NumberFormatPB) => {
|
const changeNumberFormat = async (format: NumberFormatPB) => {
|
||||||
const fieldInfo = fieldController.getField(cellIdentifier.fieldId);
|
const fieldInfo = fieldController.getField(cellIdentifier.fieldId);
|
||||||
|
|
||||||
if (!fieldInfo) return;
|
if (!fieldInfo) return;
|
||||||
const typeOptionController = new TypeOptionController(cellIdentifier.viewId, Some(fieldInfo), FieldType.Number);
|
const typeOptionController = new TypeOptionController(cellIdentifier.viewId, Some(fieldInfo), FieldType.Number);
|
||||||
|
|
||||||
await typeOptionController.initialize();
|
await typeOptionController.initialize();
|
||||||
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
||||||
const typeOption = await numberTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
const typeOption = await numberTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
||||||
|
|
||||||
typeOption.format = format;
|
typeOption.format = format;
|
||||||
await numberTypeOptionContext.setTypeOption(typeOption);
|
await numberTypeOptionContext.setTypeOption(typeOption);
|
||||||
};
|
};
|
||||||
|
@ -66,6 +66,7 @@ export const NumberFormatPopup = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNumberType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as INumberType);
|
setNumberType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as INumberType);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [databaseStore]);
|
}, [databaseStore]);
|
||||||
|
|
||||||
const changeNumberFormatClick = async (format: NumberFormatPB) => {
|
const changeNumberFormatClick = async (format: NumberFormatPB) => {
|
||||||
|
@ -28,6 +28,7 @@ export const TimeFormatPopup = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDateType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as IDateType);
|
setDateType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as IDateType);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [databaseStore]);
|
}, [databaseStore]);
|
||||||
|
|
||||||
const { changeTimeFormat } = useDateTimeFormat(cellIdentifier, fieldController);
|
const { changeTimeFormat } = useDateTimeFormat(cellIdentifier, fieldController);
|
||||||
|
@ -59,6 +59,7 @@ export const EditFieldPopup = ({
|
|||||||
const save = async () => {
|
const save = async () => {
|
||||||
if (!controller) return;
|
if (!controller) return;
|
||||||
const fieldInfo = controller.fieldController.getField(cellIdentifier.fieldId);
|
const fieldInfo = controller.fieldController.getField(cellIdentifier.fieldId);
|
||||||
|
|
||||||
if (!fieldInfo) return;
|
if (!fieldInfo) return;
|
||||||
const typeOptionController = new TypeOptionController(viewId, Some(fieldInfo));
|
const typeOptionController = new TypeOptionController(viewId, Some(fieldInfo));
|
||||||
|
|
||||||
|
@ -66,7 +66,9 @@ export const EditRow = ({
|
|||||||
const [editCheckListLeft, setEditCheckListLeft] = useState(0);
|
const [editCheckListLeft, setEditCheckListLeft] = useState(0);
|
||||||
|
|
||||||
const [showNumberFormatPopup, setShowNumberFormatPopup] = useState(false);
|
const [showNumberFormatPopup, setShowNumberFormatPopup] = useState(false);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [numberFormatTop, setNumberFormatTop] = useState(0);
|
const [numberFormatTop, setNumberFormatTop] = useState(0);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [numberFormatLeft, setNumberFormatLeft] = useState(0);
|
const [numberFormatLeft, setNumberFormatLeft] = useState(0);
|
||||||
|
|
||||||
const [showCheckListPopup, setShowCheckListPopup] = useState(false);
|
const [showCheckListPopup, setShowCheckListPopup] = useState(false);
|
||||||
|
@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
export const FieldTypeName = ({ fieldType }: { fieldType: FieldType }) => {
|
export const FieldTypeName = ({ fieldType }: { fieldType: FieldType }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{fieldType === FieldType.RichText && t('grid.field.textFieldName')}
|
{fieldType === FieldType.RichText && t('grid.field.textFieldName')}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
|
import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
|
||||||
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
|
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
|
||||||
import { CheckboxCellController } from '$app/stores/effects/database/cell/controller_builder';
|
|
||||||
|
|
||||||
export const EditCheckboxCell = ({ data, onToggle }: { data: 'Yes' | 'No' | undefined; onToggle: () => void }) => {
|
export const EditCheckboxCell = ({ data, onToggle }: { data: 'Yes' | 'No' | undefined; onToggle: () => void }) => {
|
||||||
// const toggleValue = async () => {
|
// const toggleValue = async () => {
|
||||||
|
@ -4,7 +4,6 @@ import { CellOption } from '$app/components/_shared/EditRow/Options/CellOption';
|
|||||||
import { SelectOptionPB } from '@/services/backend';
|
import { SelectOptionPB } from '@/services/backend';
|
||||||
import { useAppSelector } from '$app/stores/store';
|
import { useAppSelector } from '$app/stores/store';
|
||||||
import { KeyboardEventHandler, useEffect, useRef, useState } from 'react';
|
import { KeyboardEventHandler, useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
|
||||||
export const MultiSelectTypeOptions = ({
|
export const MultiSelectTypeOptions = ({
|
||||||
@ -16,7 +15,6 @@ export const MultiSelectTypeOptions = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const inputContainerRef = useRef<HTMLDivElement>(null);
|
const inputContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const { t } = useTranslation();
|
|
||||||
const fieldsStore = useAppSelector((state) => state.database.fields);
|
const fieldsStore = useAppSelector((state) => state.database.fields);
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const [showInput, setShowInput] = useState(false);
|
const [showInput, setShowInput] = useState(false);
|
||||||
|
@ -37,6 +37,7 @@ export const LanguageSelectPopup = ({ onClose }: { onClose: () => void }) => {
|
|||||||
title: item.title,
|
title: item.title,
|
||||||
icon: <></>,
|
icon: <></>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopupSelect
|
<PopupSelect
|
||||||
items={items}
|
items={items}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
|
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
|
||||||
import { Some } from 'ts-results';
|
import { Some } from 'ts-results';
|
||||||
import { IDatabaseField, ISelectOption } from '$app_reducers/database/slice';
|
import { IDatabaseField, ISelectOption } from '$app_reducers/database/slice';
|
||||||
import { ChecklistTypeOptionPB, FieldType, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB } from '@/services/backend';
|
import { FieldType, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB } from '@/services/backend';
|
||||||
import {
|
import {
|
||||||
makeChecklistTypeOptionContext,
|
|
||||||
makeDateTypeOptionContext,
|
makeDateTypeOptionContext,
|
||||||
makeMultiSelectTypeOptionContext,
|
makeMultiSelectTypeOptionContext,
|
||||||
makeNumberTypeOptionContext,
|
makeNumberTypeOptionContext,
|
||||||
@ -32,9 +31,11 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
|
|||||||
if (dispatch) {
|
if (dispatch) {
|
||||||
dispatch(boardActions.setGroupingFieldId({ fieldId: field.id }));
|
dispatch(boardActions.setGroupingFieldId({ fieldId: field.id }));
|
||||||
}
|
}
|
||||||
|
|
||||||
groupingFieldSelected = true;
|
groupingFieldSelected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.field_type === FieldType.MultiSelect) {
|
if (field.field_type === FieldType.MultiSelect) {
|
||||||
typeOption = (await makeMultiSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
typeOption = (await makeMultiSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||||
}
|
}
|
||||||
@ -63,6 +64,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
|
|||||||
|
|
||||||
case FieldType.Number: {
|
case FieldType.Number: {
|
||||||
const typeOption = (await makeNumberTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
const typeOption = (await makeNumberTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fieldId: field.id,
|
fieldId: field.id,
|
||||||
title: field.name,
|
title: field.name,
|
||||||
@ -77,6 +79,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
|
|||||||
|
|
||||||
case FieldType.DateTime: {
|
case FieldType.DateTime: {
|
||||||
const typeOption = (await makeDateTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
const typeOption = (await makeDateTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fieldId: field.id,
|
fieldId: field.id,
|
||||||
title: field.name,
|
title: field.name,
|
||||||
|
@ -10,6 +10,7 @@ import { databaseActions, ISelectOptionType } from '$app_reducers/database/slice
|
|||||||
|
|
||||||
export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => {
|
export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => {
|
||||||
const [data, setData] = useState<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
|
const [data, setData] = useState<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const [cellController, setCellController] = useState<CellController<any, any>>();
|
const [cellController, setCellController] = useState<CellController<any, any>>();
|
||||||
const databaseStore = useAppSelector((state) => state.database);
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -18,12 +19,14 @@ export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fi
|
|||||||
if (!cellIdentifier || !cellCache || !fieldController) return;
|
if (!cellIdentifier || !cellCache || !fieldController) return;
|
||||||
const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
|
const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
|
||||||
const c = builder.build();
|
const c = builder.build();
|
||||||
|
|
||||||
setCellController(c);
|
setCellController(c);
|
||||||
|
|
||||||
c.subscribeChanged({
|
c.subscribeChanged({
|
||||||
onCellChanged: (cellData) => {
|
onCellChanged: (cellData) => {
|
||||||
if (cellData.some) {
|
if (cellData.some) {
|
||||||
const value = cellData.val;
|
const value = cellData.val;
|
||||||
|
|
||||||
setData(value);
|
setData(value);
|
||||||
|
|
||||||
// update redux store for database field if there are new select options
|
// update redux store for database field if there are new select options
|
||||||
@ -59,6 +62,7 @@ export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fi
|
|||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const cellData = await c.getCellData();
|
const cellData = await c.getCellData();
|
||||||
|
|
||||||
if (cellData.some) {
|
if (cellData.some) {
|
||||||
setData(cellData.unwrap());
|
setData(cellData.unwrap());
|
||||||
}
|
}
|
||||||
@ -70,6 +74,7 @@ export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fi
|
|||||||
return () => {
|
return () => {
|
||||||
void c.dispose();
|
void c.dispose();
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [cellIdentifier, cellCache, fieldController]);
|
}, [cellIdentifier, cellCache, fieldController]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -186,6 +186,7 @@ export const useDatabase = (viewId: string, type?: ViewLayoutPB) => {
|
|||||||
return () => {
|
return () => {
|
||||||
void controller?.dispose();
|
void controller?.dispose();
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [controller, queue]);
|
}, [controller, queue]);
|
||||||
|
|
||||||
const onNewRowClick = async (index: number) => {
|
const onNewRowClick = async (index: number) => {
|
||||||
|
@ -18,6 +18,7 @@ export const useRow = (viewId: string, databaseController: DatabaseController, r
|
|||||||
const rowCache = databaseController.databaseViewCache.getRowCache();
|
const rowCache = databaseController.databaseViewCache.getRowCache();
|
||||||
const fieldController = databaseController.fieldController;
|
const fieldController = databaseController.fieldController;
|
||||||
const c = new RowController(rowInfo, fieldController, rowCache);
|
const c = new RowController(rowInfo, fieldController, rowCache);
|
||||||
|
|
||||||
setRowController(c);
|
setRowController(c);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -46,6 +47,7 @@ export const useRow = (viewId: string, databaseController: DatabaseController, r
|
|||||||
const onNewColumnClick = async (initialFieldType: FieldType = FieldType.RichText, name?: string) => {
|
const onNewColumnClick = async (initialFieldType: FieldType = FieldType.RichText, name?: string) => {
|
||||||
if (!databaseController) return;
|
if (!databaseController) return;
|
||||||
const controller = new TypeOptionController(viewId, None, initialFieldType);
|
const controller = new TypeOptionController(viewId, None, initialFieldType);
|
||||||
|
|
||||||
await controller.initialize();
|
await controller.initialize();
|
||||||
if (name) {
|
if (name) {
|
||||||
await controller.setFieldName(name);
|
await controller.setFieldName(name);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export default function useOutsideClick(ref: any, handler: (e: MouseEvent | TouchEvent) => void) {
|
export default function useOutsideClick(ref: any, handler: (e: MouseEvent | TouchEvent) => void) {
|
||||||
useEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
@ -8,8 +9,10 @@ export default function useOutsideClick(ref: any, handler: (e: MouseEvent | Touc
|
|||||||
if (!ref?.current || ref.current.contains(event.target)) {
|
if (!ref?.current || ref.current.contains(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handler(event);
|
handler(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('mousedown', listener);
|
document.addEventListener('mousedown', listener);
|
||||||
document.addEventListener('touchstart', listener);
|
document.addEventListener('touchstart', listener);
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { currentUserActions } from '../../../stores/reducers/current-user/slice';
|
import { currentUserActions } from '$app_reducers/current-user/slice';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
import { useAppDispatch } from '$app/stores/store';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../auth.hooks';
|
import { useAuth } from '../auth.hooks';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
@ -10,7 +10,6 @@ export const useLogin = () => {
|
|||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const appDispatch = useAppDispatch();
|
const appDispatch = useAppDispatch();
|
||||||
const currentUser = useAppSelector((state) => state.currentUser);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { login, register } = useAuth();
|
const { login, register } = useAuth();
|
||||||
const [authError, setAuthError] = useState(false);
|
const [authError, setAuthError] = useState(false);
|
||||||
@ -36,6 +35,7 @@ export const useLogin = () => {
|
|||||||
const fakePassword = 'AppFlowy123@';
|
const fakePassword = 'AppFlowy123@';
|
||||||
const userProfile = await register(fakeEmail, fakePassword, 'Me');
|
const userProfile = await register(fakeEmail, fakePassword, 'Me');
|
||||||
const { id, name, token } = userProfile;
|
const { id, name, token } = userProfile;
|
||||||
|
|
||||||
appDispatch(
|
appDispatch(
|
||||||
currentUserActions.updateUser({
|
currentUserActions.updateUser({
|
||||||
id: id,
|
id: id,
|
||||||
@ -55,6 +55,7 @@ export const useLogin = () => {
|
|||||||
try {
|
try {
|
||||||
const userProfile = await login(email, password);
|
const userProfile = await login(email, password);
|
||||||
const { id, name, token } = userProfile;
|
const { id, name, token } = userProfile;
|
||||||
|
|
||||||
appDispatch(
|
appDispatch(
|
||||||
currentUserActions.updateUser({
|
currentUserActions.updateUser({
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -21,6 +21,7 @@ export const ProtectedRoutes = () => {
|
|||||||
throw new Error(result.val.msg);
|
throw new Error(result.val.msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
import { useAppDispatch } from '$app/stores/store';
|
||||||
import { currentUserActions } from '../../../stores/reducers/current-user/slice';
|
import { currentUserActions } from '$app_reducers/current-user/slice';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../auth.hooks';
|
import { useAuth } from '../auth.hooks';
|
||||||
|
|
||||||
@ -12,7 +12,6 @@ export const useSignUp = () => {
|
|||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
const appDispatch = useAppDispatch();
|
const appDispatch = useAppDispatch();
|
||||||
const currentUser = useAppSelector((state) => state.currentUser);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { register } = useAuth();
|
const { register } = useAuth();
|
||||||
const [authError, setAuthError] = useState(false);
|
const [authError, setAuthError] = useState(false);
|
||||||
@ -49,6 +48,7 @@ export const useSignUp = () => {
|
|||||||
try {
|
try {
|
||||||
const result = await register(email, password, displayName);
|
const result = await register(email, password, displayName);
|
||||||
const { id, token } = result;
|
const { id, token } = result;
|
||||||
|
|
||||||
appDispatch(
|
appDispatch(
|
||||||
currentUserActions.updateUser({
|
currentUserActions.updateUser({
|
||||||
id,
|
id,
|
||||||
|
@ -15,6 +15,7 @@ export const BoardCheckboxCell = ({
|
|||||||
fieldController: FieldController;
|
fieldController: FieldController;
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<i className={'h-5 w-5'}>
|
<i className={'h-5 w-5'}>
|
||||||
{data === 'Yes' ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
|
{data === 'Yes' ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
|
||||||
|
@ -14,5 +14,6 @@ export const BoardDateCell = ({
|
|||||||
fieldController: FieldController;
|
fieldController: FieldController;
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
|
||||||
return <div>{(data as DateCellDataPB | undefined)?.date ?? ''} </div>;
|
return <div>{(data as DateCellDataPB | undefined)?.date ?? ''} </div>;
|
||||||
};
|
};
|
||||||
|
@ -37,6 +37,7 @@ export const BoardSettingsPopup = ({
|
|||||||
onClick: onGroupClick,
|
onClick: onGroupClick,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
import { RefObject, createContext, createRef, useContext, useCallback, useMemo, useEffect } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import { proxy, useSnapshot } from 'valtio';
|
||||||
|
import { DatabaseLayoutPB, DatabaseNotification } from '@/services/backend';
|
||||||
|
import { subscribeNotifications } from '$app/hooks';
|
||||||
|
import {
|
||||||
|
Database,
|
||||||
|
databaseService,
|
||||||
|
fieldService,
|
||||||
|
rowListeners,
|
||||||
|
sortListeners,
|
||||||
|
} from './application';
|
||||||
|
|
||||||
|
const VerticalScrollElementRefContext = createContext<RefObject<Element>>(createRef());
|
||||||
|
|
||||||
|
export const VerticalScrollElementProvider = VerticalScrollElementRefContext.Provider;
|
||||||
|
export const useVerticalScrollElement = () => useContext(VerticalScrollElementRefContext);
|
||||||
|
|
||||||
|
export function useSelectDatabaseView() {
|
||||||
|
const key = 'v';
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const selectedViewId = useMemo(() => searchParams.get(key), [searchParams]);
|
||||||
|
|
||||||
|
const selectViewId = useCallback((value: string) => {
|
||||||
|
setSearchParams({ [key]: value });
|
||||||
|
}, [setSearchParams]);
|
||||||
|
|
||||||
|
return [selectedViewId, selectViewId] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DatabaseContext = createContext<Database>({
|
||||||
|
id: '',
|
||||||
|
isLinked: false,
|
||||||
|
layoutType: DatabaseLayoutPB.Grid,
|
||||||
|
fields: [],
|
||||||
|
rowMetas: [],
|
||||||
|
filters: [],
|
||||||
|
sorts: [],
|
||||||
|
groupSettings: [],
|
||||||
|
groups: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DatabaseProvider = DatabaseContext.Provider;
|
||||||
|
|
||||||
|
export const useDatabase = () => useSnapshot(useContext(DatabaseContext));
|
||||||
|
|
||||||
|
export const useConnectDatabase = (viewId: string) => {
|
||||||
|
const database = useMemo(() => {
|
||||||
|
const proxyDatabase = proxy<Database>({
|
||||||
|
id: '',
|
||||||
|
isLinked: false,
|
||||||
|
layoutType: DatabaseLayoutPB.Grid,
|
||||||
|
fields: [],
|
||||||
|
rowMetas: [],
|
||||||
|
filters: [],
|
||||||
|
sorts: [],
|
||||||
|
groupSettings: [],
|
||||||
|
groups: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
void databaseService.openDatabase(viewId).then(value => Object.assign(proxyDatabase, value));
|
||||||
|
|
||||||
|
return proxyDatabase;
|
||||||
|
}, [viewId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribePromise = subscribeNotifications({
|
||||||
|
[DatabaseNotification.DidUpdateFields]: async () => {
|
||||||
|
database.fields = await fieldService.getFields(viewId);
|
||||||
|
},
|
||||||
|
|
||||||
|
[DatabaseNotification.DidUpdateViewRows]: changeset => rowListeners.didUpdateViewRows(database, changeset),
|
||||||
|
[DatabaseNotification.DidReorderRows]: changeset => rowListeners.didReorderRows(database, changeset),
|
||||||
|
[DatabaseNotification.DidReorderSingleRow]: changeset => rowListeners.didReorderSingleRow(database, changeset),
|
||||||
|
|
||||||
|
[DatabaseNotification.DidUpdateSort]: changeset => sortListeners.didUpdateSort(database, changeset),
|
||||||
|
}, { id: viewId });
|
||||||
|
|
||||||
|
return () => void unsubscribePromise.then(unsubscribe => unsubscribe());
|
||||||
|
}, [viewId, database]);
|
||||||
|
|
||||||
|
return database;
|
||||||
|
};
|
@ -1,41 +1,42 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { proxy } from 'valtio';
|
import { useViewId } from '$app/hooks';
|
||||||
import { subscribeKey } from 'valtio/utils';
|
import { DatabaseView as DatabaseViewType, databaseViewService } from './application';
|
||||||
import { DatabaseLayoutPB } from '@/services/backend';
|
import { DatabaseTabBar } from './components';
|
||||||
import { DndContext, DndContextDescriptor } from './_shared';
|
import { useSelectDatabaseView } from './Database.hooks';
|
||||||
import { VerticalScrollElementRefContext, DatabaseContext } from './database.context';
|
import { DatabaseLoader } from './DatabaseLoader';
|
||||||
import { useViewId, useConnectDatabase } from './database.hooks';
|
import { DatabaseView } from './DatabaseView';
|
||||||
import { DatabaseHeader } from './DatabaseHeader';
|
import { DatabaseSettings } from './components/database_settings';
|
||||||
import { Grid } from './grid';
|
|
||||||
|
|
||||||
export const Database = () => {
|
export const Database = () => {
|
||||||
const viewId = useViewId();
|
const viewId = useViewId();
|
||||||
const verticalScrollElementRef = useRef<HTMLDivElement>(null);
|
const [views, setViews] = useState<DatabaseViewType[]>([]);
|
||||||
const database = useConnectDatabase(viewId);
|
const [selectedViewId, selectViewId] = useSelectDatabaseView();
|
||||||
const [ layoutType, setLayoutType ] = useState(database.layoutType);
|
const activeView = useMemo(() => views?.find((view) => view.id === selectedViewId), [views, selectedViewId]);
|
||||||
const dndContext = useRef(proxy<DndContextDescriptor>({
|
|
||||||
dragging: null,
|
|
||||||
}));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return subscribeKey(database, 'layoutType', (value) => {
|
setViews([]);
|
||||||
setLayoutType(value);
|
void databaseViewService.getDatabaseViews(viewId).then((value) => {
|
||||||
|
setViews(value);
|
||||||
});
|
});
|
||||||
}, [database]);
|
}, [viewId]);
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div
|
if (!activeView) {
|
||||||
ref={verticalScrollElementRef}
|
const firstViewId = views?.[0]?.id;
|
||||||
className="h-full overflow-y-auto"
|
|
||||||
>
|
if (firstViewId) {
|
||||||
<DatabaseHeader />
|
selectViewId(firstViewId);
|
||||||
<VerticalScrollElementRefContext.Provider value={verticalScrollElementRef}>
|
}
|
||||||
<DndContext.Provider value={dndContext.current}>
|
}
|
||||||
<DatabaseContext.Provider value={database}>
|
}, [views, activeView, selectViewId]);
|
||||||
{layoutType === DatabaseLayoutPB.Grid ? <Grid /> : null}
|
|
||||||
</DatabaseContext.Provider>
|
return activeView ? (
|
||||||
</DndContext.Provider >
|
<DatabaseLoader viewId={viewId}>
|
||||||
</VerticalScrollElementRefContext.Provider>
|
<div className='px-16'>
|
||||||
</div>
|
<DatabaseTabBar views={views} />
|
||||||
);
|
<DatabaseSettings />
|
||||||
|
</div>
|
||||||
|
<DatabaseView />
|
||||||
|
</DatabaseLoader>
|
||||||
|
) : null;
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import { FC, PropsWithChildren } from 'react';
|
||||||
|
import { ViewIdProvider } from '$app/hooks';
|
||||||
|
import { DatabaseProvider, useConnectDatabase } from './Database.hooks';
|
||||||
|
|
||||||
|
export interface DatabaseLoaderProps {
|
||||||
|
viewId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DatabaseLoader: FC<PropsWithChildren<DatabaseLoaderProps>> = ({
|
||||||
|
viewId,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const database = useConnectDatabase(viewId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DatabaseProvider value={database}>
|
||||||
|
<ViewIdProvider value={viewId}>
|
||||||
|
{children}
|
||||||
|
</ViewIdProvider>
|
||||||
|
</DatabaseProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -1,9 +1,9 @@
|
|||||||
import { FormEventHandler, useCallback, useEffect, useMemo, useState } from 'react';
|
import { FormEventHandler, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { PageController } from '$app/stores/effects/workspace/page/page_controller';
|
import { PageController } from '$app/stores/effects/workspace/page/page_controller';
|
||||||
import { useViewId } from './database.hooks';
|
import { useViewId } from '$app/hooks';
|
||||||
|
|
||||||
export const DatabaseHeader = () => {
|
export const DatabaseTitle = () => {
|
||||||
const viewId = useViewId();
|
const viewId = useViewId();
|
||||||
const [ title, setTitle ] = useState('');
|
const [ title, setTitle ] = useState('');
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
import { DatabaseLayoutPB } from '@/services/backend';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { useDatabase } from './Database.hooks';
|
||||||
|
import { Grid } from './grid';
|
||||||
|
import { Board } from './board';
|
||||||
|
import { Calendar } from './calendar';
|
||||||
|
|
||||||
|
const ViewMap: Record<DatabaseLayoutPB, FC | null> = {
|
||||||
|
[DatabaseLayoutPB.Grid]: Grid,
|
||||||
|
[DatabaseLayoutPB.Board]: Board,
|
||||||
|
[DatabaseLayoutPB.Calendar]: Calendar,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DatabaseView: FC = () => {
|
||||||
|
const { layoutType } = useDatabase();
|
||||||
|
const View = ViewMap[layoutType];
|
||||||
|
|
||||||
|
return View && <View />;
|
||||||
|
};
|
@ -1,17 +1,11 @@
|
|||||||
import {
|
import { DragEventHandler, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||||
DragEventHandler,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { DndContext } from './dnd.context';
|
import { DndContext } from './dnd.context';
|
||||||
import { autoScrollOnEdge, EdgeGap, getScrollParent, ScrollDirection } from './utils';
|
import { autoScrollOnEdge, EdgeGap, getScrollParent, ScrollDirection } from './utils';
|
||||||
|
|
||||||
export interface UseDraggableOptions {
|
export interface UseDraggableOptions {
|
||||||
type: string;
|
type: string;
|
||||||
effectAllowed?: DataTransfer['effectAllowed'];
|
effectAllowed?: DataTransfer['effectAllowed'];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
data?: Record<string, any>;
|
data?: Record<string, any>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
scrollOnEdge?: {
|
scrollOnEdge?: {
|
||||||
@ -34,7 +28,7 @@ export const useDraggable = ({
|
|||||||
const typeRef = useRef(type);
|
const typeRef = useRef(type);
|
||||||
const dataRef = useRef(data);
|
const dataRef = useRef(data);
|
||||||
const previewRef = useRef<Element | null>(null);
|
const previewRef = useRef<Element | null>(null);
|
||||||
const [ isDragging, setIsDragging ] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
||||||
typeRef.current = type;
|
typeRef.current = type;
|
||||||
dataRef.current = data;
|
dataRef.current = data;
|
||||||
@ -53,49 +47,55 @@ export const useDraggable = ({
|
|||||||
};
|
};
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
||||||
const onDragStart = useCallback<DragEventHandler>((event) => {
|
const onDragStart = useCallback<DragEventHandler>(
|
||||||
setIsDragging(true);
|
(event) => {
|
||||||
context.dragging = {
|
setIsDragging(true);
|
||||||
type: typeRef.current,
|
context.dragging = {
|
||||||
data: dataRef.current ?? {},
|
type: typeRef.current,
|
||||||
};
|
data: dataRef.current ?? {},
|
||||||
|
};
|
||||||
|
|
||||||
const { dataTransfer } = event;
|
const { dataTransfer } = event;
|
||||||
const previewNode = previewRef.current;
|
const previewNode = previewRef.current;
|
||||||
|
|
||||||
dataTransfer.effectAllowed = effectAllowed;
|
dataTransfer.effectAllowed = effectAllowed;
|
||||||
|
|
||||||
if (previewNode) {
|
if (previewNode) {
|
||||||
const { clientX, clientY } = event;
|
const { clientX, clientY } = event;
|
||||||
const rect = previewNode.getBoundingClientRect();
|
const rect = previewNode.getBoundingClientRect();
|
||||||
|
|
||||||
dataTransfer.setDragImage(previewNode, clientX - rect.x, clientY - rect.y);
|
dataTransfer.setDragImage(previewNode, clientX - rect.x, clientY - rect.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrollDirection === undefined) {
|
if (scrollDirection === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollParent: HTMLElement | null = getScrollParent(event.target as HTMLElement, scrollDirection);
|
const scrollParent: HTMLElement | null = getScrollParent(event.target as HTMLElement, scrollDirection);
|
||||||
|
|
||||||
if (scrollParent) {
|
if (scrollParent) {
|
||||||
autoScrollOnEdge({
|
autoScrollOnEdge({
|
||||||
element: scrollParent,
|
element: scrollParent,
|
||||||
direction: scrollDirection,
|
direction: scrollDirection,
|
||||||
edgeGap,
|
edgeGap,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [ context, effectAllowed, scrollDirection, edgeGap ]);
|
},
|
||||||
|
[context, effectAllowed, scrollDirection, edgeGap]
|
||||||
|
);
|
||||||
|
|
||||||
const onDragEnd = useCallback<DragEventHandler>(() => {
|
const onDragEnd = useCallback<DragEventHandler>(() => {
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
context.dragging = null;
|
context.dragging = null;
|
||||||
}, [ context ]);
|
}, [context]);
|
||||||
|
|
||||||
const listeners = useMemo(() => ({
|
const listeners = useMemo(
|
||||||
onDragStart,
|
() => ({
|
||||||
onDragEnd,
|
onDragStart,
|
||||||
}), [ onDragStart, onDragEnd]);
|
onDragEnd,
|
||||||
|
}),
|
||||||
|
[onDragStart, onDragEnd]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isDragging,
|
isDragging,
|
||||||
|
@ -162,7 +162,6 @@ export const autoScrollOnEdge = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
console.log('document drag end');
|
|
||||||
keepScroll.cancel();
|
keepScroll.cancel();
|
||||||
|
|
||||||
element.removeEventListener('dragover', onDragOver);
|
element.removeEventListener('dragover', onDragOver);
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
import {
|
||||||
|
CellIdPB,
|
||||||
|
CellChangesetPB,
|
||||||
|
SelectOptionCellChangesetPB,
|
||||||
|
ChecklistCellDataChangesetPB,
|
||||||
|
DateChangesetPB,
|
||||||
|
FieldType,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import {
|
||||||
|
DatabaseEventGetCell,
|
||||||
|
DatabaseEventUpdateCell,
|
||||||
|
DatabaseEventUpdateSelectOptionCell,
|
||||||
|
DatabaseEventUpdateChecklistCell,
|
||||||
|
DatabaseEventUpdateDateCell,
|
||||||
|
} from '@/services/backend/events/flowy-database2';
|
||||||
|
import { SelectOption } from '../field';
|
||||||
|
import { Cell, pbToCell } from './cell_types';
|
||||||
|
|
||||||
|
export async function getCell(viewId: string, rowId: string, fieldId: string, fieldType?: FieldType): Promise<Cell> {
|
||||||
|
const payload = CellIdPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
row_id: rowId,
|
||||||
|
field_id: fieldId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventGetCell(payload);
|
||||||
|
|
||||||
|
return result.map(value => pbToCell(value, fieldType)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateCell(viewId: string, rowId: string, fieldId: string, changeset: string): Promise<void> {
|
||||||
|
const payload = CellChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
row_id: rowId,
|
||||||
|
field_id: fieldId,
|
||||||
|
cell_changeset: changeset,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateCell(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSelectCell(
|
||||||
|
viewId: string,
|
||||||
|
rowId: string,
|
||||||
|
fieldId: string,
|
||||||
|
data: {
|
||||||
|
insertOptionIds?: string[];
|
||||||
|
deleteOptionIds?: string[];
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
const payload = SelectOptionCellChangesetPB.fromObject({
|
||||||
|
cell_identifier: {
|
||||||
|
view_id: viewId,
|
||||||
|
row_id: rowId,
|
||||||
|
field_id: fieldId,
|
||||||
|
},
|
||||||
|
insert_option_ids: data.insertOptionIds,
|
||||||
|
delete_option_ids: data.deleteOptionIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateSelectOptionCell(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateChecklistCell(
|
||||||
|
viewId: string,
|
||||||
|
rowId: string,
|
||||||
|
fieldId: string,
|
||||||
|
data: {
|
||||||
|
insertOptions?: string[];
|
||||||
|
selectedOptionIds?: string[];
|
||||||
|
deleteOptionIds?: string[];
|
||||||
|
updateOptions?: Partial<SelectOption>[];
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
const payload = ChecklistCellDataChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
row_id: rowId,
|
||||||
|
field_id: fieldId,
|
||||||
|
insert_options: data.insertOptions,
|
||||||
|
selected_option_ids: data.selectedOptionIds,
|
||||||
|
delete_option_ids: data.deleteOptionIds,
|
||||||
|
update_options: data.updateOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateChecklistCell(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateDateCell(
|
||||||
|
viewId: string,
|
||||||
|
rowId: string,
|
||||||
|
fieldId: string,
|
||||||
|
data: {
|
||||||
|
date?: number;
|
||||||
|
time?: string;
|
||||||
|
includeTime?: boolean;
|
||||||
|
clearFlag?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
const payload = DateChangesetPB.fromObject({
|
||||||
|
cell_id: {
|
||||||
|
view_id: viewId,
|
||||||
|
row_id: rowId,
|
||||||
|
field_id: fieldId,
|
||||||
|
},
|
||||||
|
date: data.date,
|
||||||
|
time: data.time,
|
||||||
|
include_time: data.includeTime,
|
||||||
|
clear_flag: data.clearFlag,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateDateCell(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
import {
|
||||||
|
CellPB,
|
||||||
|
ChecklistCellDataPB,
|
||||||
|
DateCellDataPB,
|
||||||
|
FieldType,
|
||||||
|
SelectOptionCellDataPB,
|
||||||
|
URLCellDataPB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
|
||||||
|
export interface Cell {
|
||||||
|
rowId: string;
|
||||||
|
fieldId: string;
|
||||||
|
fieldType: FieldType;
|
||||||
|
data: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextCell extends Cell {
|
||||||
|
fieldType: FieldType.RichText;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumberCell extends Cell {
|
||||||
|
fieldType: FieldType.Number;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CheckboxCell extends Cell {
|
||||||
|
fieldType: FieldType.Checkbox;
|
||||||
|
data: 'Yes' | 'No';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UrlCell extends Cell {
|
||||||
|
fieldType: FieldType.URL;
|
||||||
|
data: UrlCellData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UrlCellData {
|
||||||
|
url: string;
|
||||||
|
content?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectCell extends Cell {
|
||||||
|
fieldType: FieldType.SingleSelect | FieldType.MultiSelect;
|
||||||
|
data: SelectCellData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectCellData {
|
||||||
|
selectedOptionIds?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateTimeCell extends Cell {
|
||||||
|
fieldType: FieldType.DateTime;
|
||||||
|
data: DateTimeCellData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateTimeCellData {
|
||||||
|
date?: string;
|
||||||
|
time?: string;
|
||||||
|
timestamp?: number;
|
||||||
|
includeTime?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChecklistCell extends Cell {
|
||||||
|
fieldType: FieldType.Checklist;
|
||||||
|
data: ChecklistCellData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChecklistCellData {
|
||||||
|
/**
|
||||||
|
* link to [SelectOption's id property]{@link SelectOption#id}.
|
||||||
|
*/
|
||||||
|
selectedOptions?: string[];
|
||||||
|
percentage?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UndeterminedCell = TextCell | NumberCell | DateTimeCell | SelectCell | CheckboxCell | UrlCell | ChecklistCell;
|
||||||
|
|
||||||
|
const pbToDateCellData = (pb: DateCellDataPB): DateTimeCellData => ({
|
||||||
|
date: pb.date,
|
||||||
|
time: pb.time,
|
||||||
|
timestamp: pb.timestamp,
|
||||||
|
includeTime: pb.include_time,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const pbToSelectCellData = (pb: SelectOptionCellDataPB): SelectCellData => {
|
||||||
|
return {
|
||||||
|
selectedOptionIds: pb.select_options.map(option => option.id),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const pbToURLCellData = (pb: URLCellDataPB): UrlCellData => ({
|
||||||
|
url: pb.url,
|
||||||
|
content: pb.content,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const pbToChecklistCellData = (pb: ChecklistCellDataPB): ChecklistCellData => ({
|
||||||
|
selectedOptions: pb.selected_options.map(({ id }) => id),
|
||||||
|
percentage: pb.percentage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function bytesToCellData(bytes: Uint8Array, fieldType: FieldType) {
|
||||||
|
switch (fieldType) {
|
||||||
|
case FieldType.RichText:
|
||||||
|
case FieldType.Number:
|
||||||
|
case FieldType.Checkbox:
|
||||||
|
return new TextDecoder().decode(bytes);
|
||||||
|
case FieldType.DateTime:
|
||||||
|
case FieldType.LastEditedTime:
|
||||||
|
case FieldType.CreatedTime:
|
||||||
|
return pbToDateCellData(DateCellDataPB.deserialize(bytes));
|
||||||
|
case FieldType.SingleSelect:
|
||||||
|
case FieldType.MultiSelect:
|
||||||
|
return pbToSelectCellData(SelectOptionCellDataPB.deserialize(bytes));
|
||||||
|
case FieldType.URL:
|
||||||
|
return pbToURLCellData(URLCellDataPB.deserialize(bytes));
|
||||||
|
case FieldType.Checklist:
|
||||||
|
return pbToChecklistCellData(ChecklistCellDataPB.deserialize(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pbToCell = (pb: CellPB, fieldType: FieldType = pb.field_type): Cell => {
|
||||||
|
return {
|
||||||
|
rowId: pb.row_id,
|
||||||
|
fieldId: pb.field_id,
|
||||||
|
fieldType: fieldType,
|
||||||
|
data: bytesToCellData(pb.data, fieldType),
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './cell_types';
|
||||||
|
export * as cellService from './cell_service';
|
@ -0,0 +1,87 @@
|
|||||||
|
import { DatabaseViewIdPB } from '@/services/backend';
|
||||||
|
import {
|
||||||
|
DatabaseEventGetDatabase,
|
||||||
|
DatabaseEventGetDatabaseId,
|
||||||
|
DatabaseEventGetDatabaseSetting,
|
||||||
|
} from '@/services/backend/events/flowy-database2';
|
||||||
|
import { fieldService } from '../field';
|
||||||
|
import { pbToFilter } from '../filter';
|
||||||
|
import { groupService, pbToGroupSetting } from '../group';
|
||||||
|
import { pbToRowMeta } from '../row';
|
||||||
|
import { pbToSort } from '../sort';
|
||||||
|
import { Database } from './database_types';
|
||||||
|
|
||||||
|
export async function getDatabaseId(viewId: string): Promise<string> {
|
||||||
|
const payload = DatabaseViewIdPB.fromObject({ value: viewId });
|
||||||
|
|
||||||
|
const result = await DatabaseEventGetDatabaseId(payload);
|
||||||
|
|
||||||
|
return result.map(value => value.value).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getDatabase(viewId: string) {
|
||||||
|
const payload = DatabaseViewIdPB.fromObject({
|
||||||
|
value: viewId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventGetDatabase(payload);
|
||||||
|
|
||||||
|
return result.map(value => {
|
||||||
|
return {
|
||||||
|
id: value.id,
|
||||||
|
isLinked: value.is_linked,
|
||||||
|
layoutType: value.layout_type,
|
||||||
|
fieldIds: value.fields.map(field => field.field_id),
|
||||||
|
rowMetas: value.rows.map(pbToRowMeta),
|
||||||
|
};
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDatabaseSetting(viewId: string) {
|
||||||
|
const payload = DatabaseViewIdPB.fromObject({
|
||||||
|
value: viewId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventGetDatabaseSetting(payload);
|
||||||
|
|
||||||
|
return result.map(value => {
|
||||||
|
return {
|
||||||
|
filters: value.filters.items.map(pbToFilter),
|
||||||
|
sorts: value.sorts.items.map(pbToSort),
|
||||||
|
groupSettings: value.group_settings.items.map(pbToGroupSetting),
|
||||||
|
};
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openDatabase(viewId: string): Promise<Database> {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
isLinked,
|
||||||
|
layoutType,
|
||||||
|
fieldIds,
|
||||||
|
rowMetas,
|
||||||
|
} = await getDatabase(viewId);
|
||||||
|
|
||||||
|
const {
|
||||||
|
filters,
|
||||||
|
sorts,
|
||||||
|
groupSettings,
|
||||||
|
} = await getDatabaseSetting(viewId);
|
||||||
|
|
||||||
|
const fields = await fieldService.getFields(viewId, fieldIds);
|
||||||
|
|
||||||
|
const groups = await groupService.getGroups(viewId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
isLinked,
|
||||||
|
layoutType,
|
||||||
|
fields,
|
||||||
|
rowMetas,
|
||||||
|
filters,
|
||||||
|
sorts,
|
||||||
|
groups,
|
||||||
|
groupSettings,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import { DatabaseLayoutPB } from '@/services/backend';
|
||||||
|
import { Field } from '../field';
|
||||||
|
import { Filter } from '../filter';
|
||||||
|
import { GroupSetting, Group } from '../group';
|
||||||
|
import { RowMeta } from '../row';
|
||||||
|
import { Sort } from '../sort';
|
||||||
|
|
||||||
|
export interface Database {
|
||||||
|
id: string;
|
||||||
|
isLinked: boolean;
|
||||||
|
layoutType: DatabaseLayoutPB,
|
||||||
|
fields: Field[];
|
||||||
|
rowMetas: RowMeta[];
|
||||||
|
filters: Filter[];
|
||||||
|
sorts: Sort[];
|
||||||
|
groupSettings: GroupSetting[];
|
||||||
|
groups: Group[];
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './database_types';
|
||||||
|
export * as databaseService from './database_service';
|
@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
CreateViewPayloadPB,
|
||||||
|
RepeatedViewIdPB,
|
||||||
|
UpdateViewPayloadPB,
|
||||||
|
ViewIdPB,
|
||||||
|
ViewLayoutPB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import {
|
||||||
|
FolderEventCreateView,
|
||||||
|
FolderEventDeleteView,
|
||||||
|
FolderEventReadView,
|
||||||
|
FolderEventUpdateView,
|
||||||
|
} from '@/services/backend/events/flowy-folder2';
|
||||||
|
import { databaseService } from '../database';
|
||||||
|
import { DatabaseView, DatabaseViewLayout, pbToDatabaseView } from './database_view_types';
|
||||||
|
|
||||||
|
export async function getDatabaseViews(viewId: string): Promise<DatabaseView[]> {
|
||||||
|
const payload = ViewIdPB.fromObject({ value: viewId });
|
||||||
|
|
||||||
|
const result = await FolderEventReadView(payload);
|
||||||
|
|
||||||
|
return result.map(value => {
|
||||||
|
return [
|
||||||
|
pbToDatabaseView(value),
|
||||||
|
...value.child_views.map(pbToDatabaseView),
|
||||||
|
];
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createDatabaseView(
|
||||||
|
viewId: string,
|
||||||
|
layout: DatabaseViewLayout,
|
||||||
|
name: string,
|
||||||
|
databaseId?: string,
|
||||||
|
): Promise<DatabaseView> {
|
||||||
|
const payload = CreateViewPayloadPB.fromObject({
|
||||||
|
parent_view_id: viewId,
|
||||||
|
name,
|
||||||
|
layout,
|
||||||
|
meta: {
|
||||||
|
'database_id': databaseId || await databaseService.getDatabaseId(viewId),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await FolderEventCreateView(payload);
|
||||||
|
|
||||||
|
return result.map(pbToDatabaseView).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateView(viewId: string, view: { name?: string; layout?: ViewLayoutPB }): Promise<DatabaseView> {
|
||||||
|
const payload = UpdateViewPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
name: view.name,
|
||||||
|
layout: view.layout,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await FolderEventUpdateView(payload);
|
||||||
|
|
||||||
|
return result.map(pbToDatabaseView).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteView(viewId: string): Promise<void> {
|
||||||
|
const payload = RepeatedViewIdPB.fromObject({
|
||||||
|
items: [viewId],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await FolderEventDeleteView(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { CalendarLayoutPB, ViewLayoutPB, ViewPB } from '@/services/backend';
|
||||||
|
|
||||||
|
export type DatabaseViewLayout = ViewLayoutPB.Grid | ViewLayoutPB.Board | ViewLayoutPB.Calendar;
|
||||||
|
|
||||||
|
export interface DatabaseView {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
layout: DatabaseViewLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalendarLayoutSetting {
|
||||||
|
fieldId?: string;
|
||||||
|
layoutTy?: CalendarLayoutPB;
|
||||||
|
firstDayOfWeek?: number;
|
||||||
|
showWeekends?: boolean;
|
||||||
|
showWeekNumbers?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pbToDatabaseView(viewPB: ViewPB): DatabaseView {
|
||||||
|
return {
|
||||||
|
id: viewPB.id,
|
||||||
|
layout: viewPB.layout as DatabaseViewLayout,
|
||||||
|
name: viewPB.name,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './database_view_types';
|
||||||
|
export * as databaseViewService from './database_view_service';
|
@ -0,0 +1,126 @@
|
|||||||
|
import {
|
||||||
|
CreateFieldPayloadPB,
|
||||||
|
DeleteFieldPayloadPB,
|
||||||
|
DuplicateFieldPayloadPB,
|
||||||
|
FieldChangesetPB,
|
||||||
|
FieldType,
|
||||||
|
GetFieldPayloadPB,
|
||||||
|
MoveFieldPayloadPB,
|
||||||
|
RepeatedFieldIdPB,
|
||||||
|
UpdateFieldTypePayloadPB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import {
|
||||||
|
DatabaseEventDuplicateField,
|
||||||
|
DatabaseEventUpdateField,
|
||||||
|
DatabaseEventUpdateFieldType,
|
||||||
|
DatabaseEventMoveField,
|
||||||
|
DatabaseEventGetFields,
|
||||||
|
DatabaseEventDeleteField,
|
||||||
|
DatabaseEventCreateTypeOption,
|
||||||
|
} from '@/services/backend/events/flowy-database2';
|
||||||
|
import { Field, pbToField } from './field_types';
|
||||||
|
import { bytesToTypeOption, getTypeOption } from './type_option';
|
||||||
|
|
||||||
|
export async function getFields(viewId: string, fieldIds?: string[]): Promise<Field[]> {
|
||||||
|
const payload = GetFieldPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
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();
|
||||||
|
|
||||||
|
await Promise.all(fields.map(async field => {
|
||||||
|
const typeOption = await getTypeOption(viewId, field.id, field.type);
|
||||||
|
|
||||||
|
field.typeOption = typeOption;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createField(viewId: string, fieldType?: FieldType, data?: Uint8Array): Promise<Field> {
|
||||||
|
const payload = CreateFieldPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_type: fieldType,
|
||||||
|
type_option_data: data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventCreateTypeOption(payload);
|
||||||
|
|
||||||
|
return result.map(value => {
|
||||||
|
const field = pbToField(value.field);
|
||||||
|
|
||||||
|
field.typeOption = bytesToTypeOption(value.type_option_data, field.type);
|
||||||
|
|
||||||
|
return field;
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function duplicateField(viewId: string, fieldId: string): Promise<void> {
|
||||||
|
const payload = DuplicateFieldPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventDuplicateField(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateField(viewId: string, fieldId: string, data: {
|
||||||
|
name?: string;
|
||||||
|
desc?: string;
|
||||||
|
frozen?: boolean;
|
||||||
|
visibility?: boolean;
|
||||||
|
width?: number;
|
||||||
|
}): Promise<void> {
|
||||||
|
const payload = FieldChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateField(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateFieldType(viewId: string, fieldId: string, fieldType: FieldType): Promise<void> {
|
||||||
|
const payload = UpdateFieldTypePayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
field_type: fieldType,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateFieldType(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function moveField(viewId: string, fieldId: string, fromIndex: number, toIndex: number): Promise<void> {
|
||||||
|
const payload = MoveFieldPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
from_index: fromIndex,
|
||||||
|
to_index: toIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventMoveField(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteField(viewId: string, fieldId: string): Promise<void> {
|
||||||
|
const payload = DeleteFieldPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventDeleteField(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
FieldPB,
|
||||||
|
FieldType,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import { DateTimeTypeOption, NumberTypeOption, SelectTypeOption } from './type_option/type_option_types';
|
||||||
|
|
||||||
|
export interface Field {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: FieldType;
|
||||||
|
typeOption?: unknown;
|
||||||
|
visibility: boolean;
|
||||||
|
width: number;
|
||||||
|
isPrimary: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumberField extends Field {
|
||||||
|
type: FieldType.Number;
|
||||||
|
typeOption: NumberTypeOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateTimeField extends Field {
|
||||||
|
type: FieldType.DateTime;
|
||||||
|
typeOption: DateTimeTypeOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectField extends Field {
|
||||||
|
type: FieldType.SingleSelect | FieldType.MultiSelect;
|
||||||
|
typeOption: SelectTypeOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UndeterminedField = NumberField | DateTimeField | SelectField | Field;
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
@ -0,0 +1,4 @@
|
|||||||
|
export * from './select_option';
|
||||||
|
export * from './type_option';
|
||||||
|
export * from './field_types';
|
||||||
|
export * as fieldService from './field_service';
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './select_option_types';
|
||||||
|
export * as selectOptionService from './select_option_service';
|
@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
CreateSelectOptionPayloadPB,
|
||||||
|
RepeatedSelectOptionPayload,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import {
|
||||||
|
DatabaseEventCreateSelectOption,
|
||||||
|
DatabaseEventInsertOrUpdateSelectOption,
|
||||||
|
DatabaseEventDeleteSelectOption,
|
||||||
|
} from '@/services/backend/events/flowy-database2';
|
||||||
|
import { pbToSelectOption, SelectOption } from './select_option_types';
|
||||||
|
|
||||||
|
export async function createSelectOption(viewId: string, fieldId: string, optionName: string): Promise<SelectOption> {
|
||||||
|
const payload = CreateSelectOptionPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
option_name: optionName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventCreateSelectOption(payload);
|
||||||
|
|
||||||
|
return result.map(pbToSelectOption).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param [rowId] If pass the rowId, the cell will select this option after insert or update.
|
||||||
|
*/
|
||||||
|
export async function insertOrUpdateSelectOption(
|
||||||
|
viewId: string,
|
||||||
|
fieldId: string,
|
||||||
|
items: Partial<SelectOption>[],
|
||||||
|
rowId?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const payload = RepeatedSelectOptionPayload.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
row_id: rowId,
|
||||||
|
items: items,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventInsertOrUpdateSelectOption(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteSelectOption(
|
||||||
|
viewId: string,
|
||||||
|
fieldId: string,
|
||||||
|
items: Partial<SelectOption>[],
|
||||||
|
rowId?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const payload = RepeatedSelectOptionPayload.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
row_id: rowId,
|
||||||
|
items: items,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventDeleteSelectOption(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import { SelectOptionColorPB, SelectOptionPB } from '@/services/backend';
|
||||||
|
|
||||||
|
export interface SelectOption {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
color: SelectOptionColorPB;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pbToSelectOption(pb: SelectOptionPB): SelectOption {
|
||||||
|
return {
|
||||||
|
id: pb.id,
|
||||||
|
name: pb.name,
|
||||||
|
color: pb.color,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './type_option_types';
|
||||||
|
export * from './type_option_service';
|
@ -0,0 +1,18 @@
|
|||||||
|
import {
|
||||||
|
FieldType,
|
||||||
|
TypeOptionPathPB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import { DatabaseEventGetTypeOption } from '@/services/backend/events/flowy-database2';
|
||||||
|
import { bytesToTypeOption } from './type_option_types';
|
||||||
|
|
||||||
|
export async function getTypeOption(viewId: string, fieldId: string, fieldType: FieldType) {
|
||||||
|
const payload = TypeOptionPathPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
field_type: fieldType,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventGetTypeOption(payload);
|
||||||
|
|
||||||
|
return result.map(value => bytesToTypeOption(value.type_option_data, fieldType)).unwrap();
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
import {
|
||||||
|
CheckboxTypeOptionPB,
|
||||||
|
DateFormatPB,
|
||||||
|
FieldType,
|
||||||
|
MultiSelectTypeOptionPB,
|
||||||
|
NumberFormatPB,
|
||||||
|
NumberTypeOptionPB,
|
||||||
|
RichTextTypeOptionPB,
|
||||||
|
SingleSelectTypeOptionPB,
|
||||||
|
TimeFormatPB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import { pbToSelectOption, SelectOption } from '../select_option';
|
||||||
|
|
||||||
|
export interface TextTypeOption {
|
||||||
|
data?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumberTypeOption {
|
||||||
|
format?: NumberFormatPB;
|
||||||
|
scale?: number;
|
||||||
|
symbol?: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateTimeTypeOption {
|
||||||
|
dateFormat?: DateFormatPB;
|
||||||
|
timeFormat?: TimeFormatPB;
|
||||||
|
timezoneId?: string;
|
||||||
|
fieldType?: FieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectTypeOption {
|
||||||
|
options?: SelectOption[];
|
||||||
|
disableColor?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CheckboxTypeOption {
|
||||||
|
isSelected?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function pbToSelectTypeOption(pb: SingleSelectTypeOptionPB | MultiSelectTypeOptionPB): SelectTypeOption {
|
||||||
|
return {
|
||||||
|
options: pb.options?.map(pbToSelectOption),
|
||||||
|
disableColor: pb.disable_color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function pbToCheckboxTypeOption(pb: CheckboxTypeOptionPB): CheckboxTypeOption {
|
||||||
|
return {
|
||||||
|
isSelected: pb.is_selected,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bytesToTypeOption(data: Uint8Array, fieldType: FieldType) {
|
||||||
|
switch (fieldType) {
|
||||||
|
case FieldType.RichText:
|
||||||
|
return RichTextTypeOptionPB.deserialize(data).toObject() as TextTypeOption;
|
||||||
|
case FieldType.Number:
|
||||||
|
return NumberTypeOptionPB.deserialize(data).toObject() as NumberTypeOption;
|
||||||
|
case FieldType.SingleSelect:
|
||||||
|
return pbToSelectTypeOption(SingleSelectTypeOptionPB.deserialize(data));
|
||||||
|
case FieldType.MultiSelect:
|
||||||
|
return pbToSelectTypeOption(MultiSelectTypeOptionPB.deserialize(data));
|
||||||
|
case FieldType.Checkbox:
|
||||||
|
return pbToCheckboxTypeOption(CheckboxTypeOptionPB.deserialize(data));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
DatabaseEventGetAllFilters,
|
||||||
|
DatabaseEventUpdateDatabaseSetting,
|
||||||
|
DatabaseSettingChangesetPB,
|
||||||
|
DatabaseViewIdPB,
|
||||||
|
DeleteFilterPayloadPB,
|
||||||
|
FieldType,
|
||||||
|
FilterPB,
|
||||||
|
UpdateFilterPayloadPB,
|
||||||
|
} from '@/services/backend/events/flowy-database2';
|
||||||
|
import { Filter, filterDataToPB, UndeterminedFilter } from './filter_types';
|
||||||
|
|
||||||
|
export async function getAllFilters(viewId: string): Promise<FilterPB[]> {
|
||||||
|
const payload = DatabaseViewIdPB.fromObject({ value: viewId });
|
||||||
|
|
||||||
|
const result = await DatabaseEventGetAllFilters(payload);
|
||||||
|
|
||||||
|
return result.map(value => value.items).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function insertFilter(
|
||||||
|
viewId: string,
|
||||||
|
fieldId: string,
|
||||||
|
fieldType: FieldType,
|
||||||
|
data: UndeterminedFilter['data'],
|
||||||
|
): Promise<void> {
|
||||||
|
const payload = DatabaseSettingChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
update_filter: UpdateFilterPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
field_type: fieldType,
|
||||||
|
data: filterDataToPB(data, fieldType)?.serialize(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateDatabaseSetting(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateFilter(viewId: string, filter: UndeterminedFilter): Promise<void> {
|
||||||
|
const payload = DatabaseSettingChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
update_filter: UpdateFilterPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
filter_id: filter.id,
|
||||||
|
field_id: filter.fieldId,
|
||||||
|
field_type: filter.fieldType,
|
||||||
|
data: filterDataToPB(filter.data, filter.fieldType)?.serialize(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateDatabaseSetting(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteFilter(viewId: string, filter: Omit<Filter, 'data'>): Promise<void> {
|
||||||
|
const payload = DatabaseSettingChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
delete_filter: DeleteFilterPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
filter_id: filter.id,
|
||||||
|
field_id: filter.fieldId,
|
||||||
|
field_type: filter.fieldType,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateDatabaseSetting(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
import {
|
||||||
|
FieldType,
|
||||||
|
TextFilterConditionPB,
|
||||||
|
SelectOptionConditionPB,
|
||||||
|
TextFilterPB,
|
||||||
|
SelectOptionFilterPB,
|
||||||
|
FilterPB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
|
||||||
|
export interface Filter {
|
||||||
|
id: string;
|
||||||
|
fieldId: string;
|
||||||
|
fieldType: FieldType;
|
||||||
|
data: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextFilter extends Filter {
|
||||||
|
fieldType: FieldType.RichText;
|
||||||
|
data: TextFilterData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextFilterData {
|
||||||
|
condition: TextFilterConditionPB;
|
||||||
|
content?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectFilter extends Filter {
|
||||||
|
fieldType: FieldType.SingleSelect | FieldType.MultiSelect;
|
||||||
|
data: SelectFilterData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectFilterData {
|
||||||
|
condition?: SelectOptionConditionPB;
|
||||||
|
optionIds?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UndeterminedFilter = TextFilter | SelectFilter;
|
||||||
|
|
||||||
|
export function filterDataToPB(data: UndeterminedFilter['data'], fieldType: FieldType) {
|
||||||
|
switch (fieldType) {
|
||||||
|
case FieldType.RichText:
|
||||||
|
return TextFilterPB.fromObject({
|
||||||
|
condition: (data as TextFilterData).condition,
|
||||||
|
content: (data as TextFilterData).content,
|
||||||
|
});
|
||||||
|
case FieldType.SingleSelect:
|
||||||
|
case FieldType.MultiSelect:
|
||||||
|
return SelectOptionFilterPB.fromObject({
|
||||||
|
condition: (data as SelectFilterData).condition,
|
||||||
|
option_ids: (data as SelectFilterData).optionIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pbToTextFilterData(pb: TextFilterPB): TextFilterData {
|
||||||
|
return {
|
||||||
|
condition: pb.condition,
|
||||||
|
content: pb.content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pbToSelectFilterData(pb: SelectOptionFilterPB): SelectFilterData {
|
||||||
|
return {
|
||||||
|
condition: pb.condition,
|
||||||
|
optionIds: pb.option_ids,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bytesToFilterData(bytes: Uint8Array, fieldType: FieldType) {
|
||||||
|
switch (fieldType) {
|
||||||
|
case FieldType.RichText:
|
||||||
|
return pbToTextFilterData(TextFilterPB.deserialize(bytes));
|
||||||
|
case FieldType.SingleSelect:
|
||||||
|
case FieldType.MultiSelect:
|
||||||
|
return pbToSelectFilterData(SelectOptionFilterPB.deserialize(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pbToFilter(pb: FilterPB): Filter {
|
||||||
|
return {
|
||||||
|
id: pb.id,
|
||||||
|
fieldId: pb.field_id,
|
||||||
|
fieldType: pb.field_type,
|
||||||
|
data: bytesToFilterData(pb.data, pb.field_type),
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './filter_types';
|
||||||
|
export * as filterService from './filter_service';
|
@ -0,0 +1,64 @@
|
|||||||
|
import {
|
||||||
|
DatabaseViewIdPB,
|
||||||
|
GroupByFieldPayloadPB,
|
||||||
|
MoveGroupPayloadPB,
|
||||||
|
UpdateGroupPB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import {
|
||||||
|
DatabaseEventGetGroups,
|
||||||
|
DatabaseEventMoveGroup,
|
||||||
|
DatabaseEventSetGroupByField,
|
||||||
|
DatabaseEventUpdateGroup,
|
||||||
|
} from '@/services/backend/events/flowy-database2';
|
||||||
|
import { Group, pbToGroup } from './group_types';
|
||||||
|
|
||||||
|
export async function getGroups(viewId: string): Promise<Group[]> {
|
||||||
|
const payload = DatabaseViewIdPB.fromObject({ value: viewId });
|
||||||
|
|
||||||
|
const result = await DatabaseEventGetGroups(payload);
|
||||||
|
|
||||||
|
return result.map(value => value.items.map(pbToGroup)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setGroupByField(viewId: string, fieldId: string): Promise<void> {
|
||||||
|
const payload = GroupByFieldPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventSetGroupByField(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateGroup(
|
||||||
|
viewId: string,
|
||||||
|
group: {
|
||||||
|
id: string,
|
||||||
|
name?: string,
|
||||||
|
visible?: boolean,
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
const payload = UpdateGroupPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
group_id: group.id,
|
||||||
|
name: group.name,
|
||||||
|
visible: group.visible,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateGroup(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function moveGroup(viewId: string, fromGroupId: string, toGroupId: string): Promise<void> {
|
||||||
|
const payload = MoveGroupPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
from_group_id: fromGroupId,
|
||||||
|
to_group_id: toGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventMoveGroup(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
import { GroupPB, GroupSettingPB } from '@/services/backend';
|
||||||
|
import { pbToRowMeta, RowMeta } from '../row';
|
||||||
|
|
||||||
|
export interface GroupSetting {
|
||||||
|
id: string;
|
||||||
|
fieldId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Group {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isDefault: boolean;
|
||||||
|
isVisible: boolean;
|
||||||
|
fieldId: string;
|
||||||
|
rows: RowMeta[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pbToGroup(pb: GroupPB): Group {
|
||||||
|
return {
|
||||||
|
id: pb.group_id,
|
||||||
|
name: pb.group_name,
|
||||||
|
isDefault: pb.is_default,
|
||||||
|
isVisible: pb.is_visible,
|
||||||
|
fieldId: pb.field_id,
|
||||||
|
rows: pb.rows.map(pbToRowMeta),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pbToGroupSetting(pb: GroupSettingPB): GroupSetting {
|
||||||
|
return {
|
||||||
|
id: pb.id,
|
||||||
|
fieldId: pb.field_id,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './group_types';
|
||||||
|
export * as groupService from './group_service';
|
@ -0,0 +1,8 @@
|
|||||||
|
export * from './cell';
|
||||||
|
export * from './database';
|
||||||
|
export * from './database_view';
|
||||||
|
export * from './field';
|
||||||
|
export * from './filter';
|
||||||
|
export * from './group';
|
||||||
|
export * from './row';
|
||||||
|
export * from './sort';
|
@ -0,0 +1,3 @@
|
|||||||
|
export * from './row_types';
|
||||||
|
export * as rowService from './row_service';
|
||||||
|
export * as rowListeners from './row_listeners';
|
@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
ReorderAllRowsPB,
|
||||||
|
ReorderSingleRowPB,
|
||||||
|
RowsChangePB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import { Database } from '../database';
|
||||||
|
import { pbToRowMeta, RowMeta } from './row_types';
|
||||||
|
|
||||||
|
const deleteRowsFromChangeset = (database: Database, changeset: RowsChangePB) => {
|
||||||
|
changeset.deleted_rows.forEach(rowId => {
|
||||||
|
const index = database.rowMetas.findIndex(row => row.id === rowId);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
database.rowMetas.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertRowsFromChangeset = (database: Database, changeset: RowsChangePB) => {
|
||||||
|
changeset.inserted_rows.forEach(({ index, row_meta: rowMetaPB }) => {
|
||||||
|
database.rowMetas.splice(index, 0, pbToRowMeta(rowMetaPB));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateRowsFromChangeset = (database: Database, changeset: RowsChangePB) => {
|
||||||
|
changeset.updated_rows.forEach(({ row_id: rowId, row_meta: rowMetaPB }) => {
|
||||||
|
const found = database.rowMetas.find(rowMeta => rowMeta.id === rowId);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
Object.assign(found, pbToRowMeta(rowMetaPB));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const didUpdateViewRows = (database: Database, changeset: RowsChangePB) => {
|
||||||
|
deleteRowsFromChangeset(database, changeset);
|
||||||
|
insertRowsFromChangeset(database, changeset);
|
||||||
|
updateRowsFromChangeset(database, changeset);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const didReorderRows = (database: Database, changeset: ReorderAllRowsPB) => {
|
||||||
|
const rowById = database.rowMetas.reduce<Record<string, RowMeta>>((prev, cur) => {
|
||||||
|
prev[cur.id] = cur;
|
||||||
|
return prev;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
database.rowMetas = changeset.row_orders.map(rowId => rowById[rowId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const didReorderSingleRow = (database: Database, changeset: ReorderSingleRowPB) => {
|
||||||
|
const {
|
||||||
|
row_id: rowId,
|
||||||
|
new_index: newIndex,
|
||||||
|
} = changeset;
|
||||||
|
|
||||||
|
const oldIndex = database.rowMetas.findIndex(rowMeta => rowMeta.id === rowId);
|
||||||
|
|
||||||
|
if (oldIndex !== -1) {
|
||||||
|
database.rowMetas.splice(newIndex, 0, database.rowMetas.splice(oldIndex, 1)[0]);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,124 @@
|
|||||||
|
import {
|
||||||
|
CreateRowPayloadPB,
|
||||||
|
MoveGroupRowPayloadPB,
|
||||||
|
MoveRowPayloadPB,
|
||||||
|
RowIdPB,
|
||||||
|
UpdateRowMetaChangesetPB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import {
|
||||||
|
DatabaseEventCreateRow,
|
||||||
|
DatabaseEventDeleteRow,
|
||||||
|
DatabaseEventDuplicateRow,
|
||||||
|
DatabaseEventGetRowMeta,
|
||||||
|
DatabaseEventMoveGroupRow,
|
||||||
|
DatabaseEventMoveRow,
|
||||||
|
DatabaseEventUpdateRowMeta,
|
||||||
|
} from '@/services/backend/events/flowy-database2';
|
||||||
|
import { pbToRowMeta, RowMeta } from './row_types';
|
||||||
|
|
||||||
|
export async function createRow(viewId: string, params?: {
|
||||||
|
startRowId?: string;
|
||||||
|
groupId?: string;
|
||||||
|
data?: Record<string, string>;
|
||||||
|
}): Promise<RowMeta> {
|
||||||
|
const payload = CreateRowPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
start_row_id: params?.startRowId,
|
||||||
|
group_id: params?.groupId,
|
||||||
|
data: params?.data ? { cell_data_by_field_id: params.data } : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventCreateRow(payload);
|
||||||
|
|
||||||
|
return result.map(pbToRowMeta).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function duplicateRow(viewId: string, rowId: string, groupId?: string): Promise<void> {
|
||||||
|
const payload = RowIdPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
row_id: rowId,
|
||||||
|
group_id: groupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventDuplicateRow(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteRow(viewId: string, rowId: string, groupId?: string): Promise<void> {
|
||||||
|
const payload = RowIdPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
row_id: rowId,
|
||||||
|
group_id: groupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventDeleteRow(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function moveRow(viewId: string, fromRowId: string, toRowId: string): Promise<void> {
|
||||||
|
const payload = MoveRowPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
from_row_id: fromRowId,
|
||||||
|
to_row_id: toRowId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventMoveRow(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the row from one group to another group
|
||||||
|
*
|
||||||
|
* @param fromRowId
|
||||||
|
* @param toGroupId
|
||||||
|
* @param toRowId used to locate the moving row location.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function moveGroupRow(viewId: string, fromRowId: string, toGroupId: string, toRowId?: string): Promise<void> {
|
||||||
|
const payload = MoveGroupRowPayloadPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
from_row_id: fromRowId,
|
||||||
|
to_group_id: toGroupId,
|
||||||
|
to_row_id: toRowId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventMoveGroupRow(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getRowMeta(viewId: string, rowId: string, groupId?: string): Promise<RowMeta> {
|
||||||
|
const payload = RowIdPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
row_id: rowId,
|
||||||
|
group_id: groupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventGetRowMeta(payload);
|
||||||
|
|
||||||
|
return result.map(pbToRowMeta).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateRowMeta(
|
||||||
|
viewId: string,
|
||||||
|
rowId: string,
|
||||||
|
meta: {
|
||||||
|
iconUrl?: string;
|
||||||
|
coverUrl?: string;
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
const payload = UpdateRowMetaChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
id: rowId,
|
||||||
|
icon_url: meta.iconUrl,
|
||||||
|
cover_url: meta.coverUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateRowMeta(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
import { RowMetaPB } from '@/services/backend';
|
||||||
|
|
||||||
|
export interface RowMeta {
|
||||||
|
id: string;
|
||||||
|
documentId?: string;
|
||||||
|
icon?: string;
|
||||||
|
cover?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pbToRowMeta(pb: RowMetaPB): RowMeta {
|
||||||
|
const rowMeta: RowMeta = {
|
||||||
|
id: pb.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pb.document_id) {
|
||||||
|
rowMeta.documentId = pb.document_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pb.icon) {
|
||||||
|
rowMeta.icon = pb.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pb.cover) {
|
||||||
|
rowMeta.cover = pb.cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowMeta;
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export * from './sort_types';
|
||||||
|
export * as sortService from './sort_service';
|
||||||
|
export * as sortListeners from './sort_listeners';
|
@ -0,0 +1,35 @@
|
|||||||
|
import { SortChangesetNotificationPB } from '@/services/backend';
|
||||||
|
import { Database } from '../database';
|
||||||
|
import { pbToSort } from './sort_types';
|
||||||
|
|
||||||
|
const deleteSortsFromChange = (database: Database, changeset: SortChangesetNotificationPB) => {
|
||||||
|
const deleteIds = changeset.delete_sorts.map(sort => sort.id);
|
||||||
|
|
||||||
|
if (deleteIds.length) {
|
||||||
|
database.sorts = database.sorts.filter(sort => !deleteIds.includes(sort.id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertSortsFromChange = (database: Database, changeset: SortChangesetNotificationPB) => {
|
||||||
|
changeset.insert_sorts.forEach(sortPB => {
|
||||||
|
database.sorts.push(pbToSort(sortPB));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSortsFromChange = (database: Database, changeset: SortChangesetNotificationPB) => {
|
||||||
|
changeset.update_sorts.forEach(sortPB => {
|
||||||
|
const found = database.sorts.find(sort => sort.id === sortPB.id);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
const newSort = pbToSort(sortPB);
|
||||||
|
|
||||||
|
Object.assign(found, newSort);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const didUpdateSort = (database: Database, changeset: SortChangesetNotificationPB) => {
|
||||||
|
deleteSortsFromChange(database, changeset);
|
||||||
|
insertSortsFromChange(database, changeset);
|
||||||
|
updateSortsFromChange(database, changeset);
|
||||||
|
};
|
@ -0,0 +1,77 @@
|
|||||||
|
import {
|
||||||
|
DatabaseViewIdPB,
|
||||||
|
DatabaseSettingChangesetPB,
|
||||||
|
} from '@/services/backend';
|
||||||
|
import {
|
||||||
|
DatabaseEventDeleteAllSorts,
|
||||||
|
DatabaseEventGetAllSorts, DatabaseEventUpdateDatabaseSetting,
|
||||||
|
} from '@/services/backend/events/flowy-database2';
|
||||||
|
import { pbToSort, Sort } from './sort_types';
|
||||||
|
|
||||||
|
export async function getAllSorts(viewId: string): Promise<Sort[]> {
|
||||||
|
const payload = DatabaseViewIdPB.fromObject({
|
||||||
|
value: viewId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventGetAllSorts(payload);
|
||||||
|
|
||||||
|
return result.map(value => value.items.map(pbToSort)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function insertSort(viewId: string, sort: Omit<Sort, 'id'>): Promise<void> {
|
||||||
|
const payload = DatabaseSettingChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
update_sort: {
|
||||||
|
view_id: viewId,
|
||||||
|
field_id: sort.fieldId,
|
||||||
|
field_type: sort.fieldType,
|
||||||
|
condition: sort.condition,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateDatabaseSetting(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSort(viewId: string, sort: Sort): Promise<void> {
|
||||||
|
const payload = DatabaseSettingChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
update_sort: {
|
||||||
|
view_id: viewId,
|
||||||
|
sort_id: sort.id,
|
||||||
|
field_id: sort.fieldId,
|
||||||
|
field_type: sort.fieldType,
|
||||||
|
condition: sort.condition,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateDatabaseSetting(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteSort(viewId: string, sort: Sort): Promise<void> {
|
||||||
|
const payload = DatabaseSettingChangesetPB.fromObject({
|
||||||
|
view_id: viewId,
|
||||||
|
delete_sort: {
|
||||||
|
view_id: viewId,
|
||||||
|
sort_id: sort.id,
|
||||||
|
field_id: sort.fieldId,
|
||||||
|
field_type: sort.fieldType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await DatabaseEventUpdateDatabaseSetting(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteAllSorts(viewId: string): Promise<void> {
|
||||||
|
const payload = DatabaseViewIdPB.fromObject({
|
||||||
|
value: viewId,
|
||||||
|
});
|
||||||
|
const result = await DatabaseEventDeleteAllSorts(payload);
|
||||||
|
|
||||||
|
return result.unwrap();
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import { FieldType, SortConditionPB, SortPB } from '@/services/backend';
|
||||||
|
|
||||||
|
export interface Sort {
|
||||||
|
id: string;
|
||||||
|
fieldId: string;
|
||||||
|
fieldType: FieldType;
|
||||||
|
condition: SortConditionPB;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pbToSort(pb: SortPB): Sort {
|
||||||
|
return {
|
||||||
|
id: pb.id,
|
||||||
|
fieldId: pb.field_id,
|
||||||
|
fieldType: pb.field_type,
|
||||||
|
condition: pb.condition,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
|
||||||
|
export const Board: FC = () => {
|
||||||
|
return null;
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './Board';
|
@ -0,0 +1,5 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
|
||||||
|
export const Calendar: FC = () => {
|
||||||
|
return null;
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './Calendar';
|
@ -0,0 +1,24 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { DatabaseNotification, FieldType } from '@/services/backend';
|
||||||
|
import { useNotification, useViewId } from '$app/hooks';
|
||||||
|
import { cellService, Cell } from '../../application';
|
||||||
|
|
||||||
|
export const useCell = (rowId: string, fieldId: string, fieldType: FieldType) => {
|
||||||
|
const viewId = useViewId();
|
||||||
|
const [cell, setCell] = useState<Cell | null>(null);
|
||||||
|
|
||||||
|
const fetchCell = useCallback(() => {
|
||||||
|
void cellService.getCell(viewId, rowId, fieldId, fieldType)
|
||||||
|
.then(data => {
|
||||||
|
setCell(data);
|
||||||
|
});
|
||||||
|
}, [viewId, rowId, fieldId, fieldType]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void fetchCell();
|
||||||
|
}, [fetchCell]);
|
||||||
|
|
||||||
|
useNotification(DatabaseNotification.DidUpdateCell, fetchCell, { id: `${rowId}:${fieldId}` });
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
};
|
@ -0,0 +1,42 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { FieldType } from '@/services/backend';
|
||||||
|
|
||||||
|
import { Cell as CellType, Field } from '../../application';
|
||||||
|
import { useCell } from './Cell.hooks';
|
||||||
|
import { TextCell } from './TextCell';
|
||||||
|
import { SelectCell } from './SelectCell';
|
||||||
|
import { CheckboxCell } from './CheckboxCell';
|
||||||
|
|
||||||
|
export interface CellProps {
|
||||||
|
rowId: string;
|
||||||
|
field: Field;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCellComponent = (fieldType: FieldType) => {
|
||||||
|
switch (fieldType) {
|
||||||
|
case FieldType.RichText:
|
||||||
|
return TextCell as FC<{ field: Field, cell: CellType }>;
|
||||||
|
case FieldType.SingleSelect:
|
||||||
|
case FieldType.MultiSelect:
|
||||||
|
return SelectCell as FC<{ field: Field, cell: CellType }>;
|
||||||
|
case FieldType.Checkbox:
|
||||||
|
return CheckboxCell as FC<{ field: Field, cell: CellType }>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Cell: FC<CellProps> = ({
|
||||||
|
rowId,
|
||||||
|
field,
|
||||||
|
}) => {
|
||||||
|
const cell = useCell(rowId, field.id, field.type);
|
||||||
|
|
||||||
|
const Component = getCellComponent(field.type);
|
||||||
|
|
||||||
|
if (!cell || !Component) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Component field={field} cell={cell} />;
|
||||||
|
};
|
@ -0,0 +1,35 @@
|
|||||||
|
import { Checkbox } from '@mui/material';
|
||||||
|
import { FC, useCallback } from 'react';
|
||||||
|
import { ReactComponent as CheckboxCheckSvg } from '$app/assets/database/checkbox-check.svg';
|
||||||
|
import { ReactComponent as CheckboxUncheckSvg } from '$app/assets/database/checkbox-uncheck.svg';
|
||||||
|
import { useViewId } from '$app/hooks';
|
||||||
|
import { cellService, CheckboxCell as CheckboxCellType, Field } from '../../application';
|
||||||
|
|
||||||
|
export const CheckboxCell: FC<{
|
||||||
|
field: Field,
|
||||||
|
cell: CheckboxCellType,
|
||||||
|
}> = ({ field, cell }) => {
|
||||||
|
const viewId = useViewId();
|
||||||
|
const checked = cell.data === 'Yes';
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
void cellService.updateCell(
|
||||||
|
viewId,
|
||||||
|
cell.rowId,
|
||||||
|
field.id,
|
||||||
|
!checked ? 'Yes' : 'No',
|
||||||
|
);
|
||||||
|
}, [viewId, cell.rowId, field.id, checked ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center w-full px-2 cursor-pointer" onClick={handleClick}>
|
||||||
|
<Checkbox
|
||||||
|
disableRipple
|
||||||
|
style={{ padding: 0 }}
|
||||||
|
checked={checked}
|
||||||
|
icon={<CheckboxUncheckSvg />}
|
||||||
|
checkedIcon={<CheckboxCheckSvg />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -10,9 +10,8 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { FieldType } from '@/services/backend';
|
import { FieldType } from '@/services/backend';
|
||||||
import { Database } from '$app/interfaces/database';
|
import { useViewId } from '$app/hooks';
|
||||||
import * as service from '../../../database_bd_svc';
|
import { cellService, SelectField, SelectCell as SelectCellType } from '../../../application';
|
||||||
import { useViewId } from '../../../database.hooks';
|
|
||||||
import { Tag } from './Tag';
|
import { Tag } from './Tag';
|
||||||
import { CreateOption } from './CreateOption';
|
import { CreateOption } from './CreateOption';
|
||||||
import { SelectOptionItem } from './SelectOptionItem';
|
import { SelectOptionItem } from './SelectOptionItem';
|
||||||
@ -31,14 +30,14 @@ const menuProps: Partial<MenuProps> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GridSelectCell: FC<{
|
export const SelectCell: FC<{
|
||||||
rowId: string;
|
field: SelectField;
|
||||||
field: Database.Field;
|
cell: SelectCellType;
|
||||||
cell: Database.SelectCell | null;
|
}> = ({ field, cell }) => {
|
||||||
}> = ({ rowId, field, cell }) => {
|
const rowId = cell.rowId;
|
||||||
const viewId = useViewId();
|
const viewId = useViewId();
|
||||||
const options = useMemo(() => cell?.data?.options ?? [], [cell?.data.options]);
|
const options = useMemo(() => field.typeOption.options ?? [], [field.typeOption.options]);
|
||||||
const selectedIds = useMemo(() => cell?.data.selectOptions?.map(({ id }) => id) ?? [], [cell?.data.selectOptions]);
|
const selectedIds = useMemo(() => cell.data.selectedOptionIds ?? [], [cell.data.selectedOptionIds]);
|
||||||
const [newOptionName, setNewOptionName] = useState('');
|
const [newOptionName, setNewOptionName] = useState('');
|
||||||
const filteredOptions = useMemo(() => options.filter(option => {
|
const filteredOptions = useMemo(() => options.filter(option => {
|
||||||
return option.name.toLowerCase().includes(newOptionName.toLowerCase());
|
return option.name.toLowerCase().includes(newOptionName.toLowerCase());
|
||||||
@ -60,10 +59,10 @@ export const GridSelectCell: FC<{
|
|||||||
const { target: { value } } = event;
|
const { target: { value } } = event;
|
||||||
|
|
||||||
const current = Array.isArray(value) ? value : [value];
|
const current = Array.isArray(value) ? value : [value];
|
||||||
const prev = cell?.data.selectOptions?.map(({ id }) => id);
|
const prev = cell.data.selectedOptionIds;
|
||||||
const deleteOptionIds = prev?.filter(id => current.find(cur => cur === id) === undefined);
|
const deleteOptionIds = prev?.filter(id => current.find(cur => cur === id) === undefined);
|
||||||
|
|
||||||
void service.updateSelectOptionCell(viewId, rowId, field.id, {
|
void cellService.updateSelectCell(viewId, rowId, field.id, {
|
||||||
insertOptionIds: current,
|
insertOptionIds: current,
|
||||||
deleteOptionIds,
|
deleteOptionIds,
|
||||||
});
|
});
|
||||||
@ -73,14 +72,14 @@ export const GridSelectCell: FC<{
|
|||||||
const exist = options.find(option => option.name.toLowerCase() === newOptionName.toLowerCase());
|
const exist = options.find(option => option.name.toLowerCase() === newOptionName.toLowerCase());
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
return service.updateSelectOptionCell(viewId, rowId, field.id, {
|
return cellService.updateSelectCell(viewId, rowId, field.id, {
|
||||||
insertOptionIds: [exist.id],
|
insertOptionIds: [exist.id],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const option = await service.createSelectOption(viewId, field.id, newOptionName);
|
// const option = await cellService.createSelectOption(viewId, field.id, newOptionName);
|
||||||
|
|
||||||
await service.insertOrUpdateSelectOption(viewId, field.id, [option], rowId);
|
// await cellService.insertOrUpdateSelectOption(viewId, field.id, [option], rowId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchInput = (
|
const searchInput = (
|
@ -1,12 +1,12 @@
|
|||||||
import { FC, MouseEventHandler, useCallback, useRef, useState } from 'react';
|
import { FC, MouseEventHandler, useCallback, useRef, useState } from 'react';
|
||||||
import { IconButton } from '@mui/material';
|
import { IconButton } from '@mui/material';
|
||||||
import { Database } from '$app/interfaces/database';
|
|
||||||
import { ReactComponent as DetailsSvg } from '$app/assets/details.svg';
|
import { ReactComponent as DetailsSvg } from '$app/assets/details.svg';
|
||||||
|
import { SelectOption } from '../../../application';
|
||||||
import { SelectOptionMenu } from './SelectOptionMenu';
|
import { SelectOptionMenu } from './SelectOptionMenu';
|
||||||
import { Tag } from './Tag';
|
import { Tag } from './Tag';
|
||||||
|
|
||||||
export interface SelectOptionItemProps {
|
export interface SelectOptionItemProps {
|
||||||
option: Database.SelectOption;
|
option: SelectOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectOptionItem: FC<SelectOptionItemProps> = ({
|
export const SelectOptionItem: FC<SelectOptionItemProps> = ({
|
||||||
@ -46,4 +46,4 @@ export const SelectOptionItem: FC<SelectOptionItemProps> = ({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -1,14 +1,21 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { Divider, ListSubheader, Menu, MenuItem, MenuProps, OutlinedInput } from '@mui/material'
|
import {
|
||||||
|
Divider,
|
||||||
|
ListSubheader,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuProps,
|
||||||
|
OutlinedInput,
|
||||||
|
} from '@mui/material';
|
||||||
import { SelectOptionColorPB } from '@/services/backend';
|
import { SelectOptionColorPB } from '@/services/backend';
|
||||||
import { ReactComponent as DeleteSvg } from '$app/assets/delete.svg';
|
import { ReactComponent as DeleteSvg } from '$app/assets/delete.svg';
|
||||||
import { Database } from '$app/interfaces/database';
|
|
||||||
import { SelectOptionColorMap, SelectOptionColorTextMap } from './constants';
|
|
||||||
import { ReactComponent as SelectCheckSvg } from '$app/assets/database/select-check.svg';
|
import { ReactComponent as SelectCheckSvg } from '$app/assets/database/select-check.svg';
|
||||||
|
import { SelectOption } from '../../../application';
|
||||||
|
import { SelectOptionColorMap, SelectOptionColorTextMap } from './constants';
|
||||||
|
|
||||||
interface SelectOptionMenuProps {
|
interface SelectOptionMenuProps {
|
||||||
option: Database.SelectOption;
|
option: SelectOption;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
MenuProps?: Partial<MenuProps>;
|
MenuProps?: Partial<MenuProps>;
|
||||||
}
|
}
|
||||||
@ -67,5 +74,5 @@ export const SelectOptionMenu: FC<SelectOptionMenuProps> = ({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
)
|
);
|
||||||
}
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { SelectOptionColorPB } from "@/services/backend";
|
import { SelectOptionColorPB } from '@/services/backend';
|
||||||
|
|
||||||
export const SelectOptionColorMap = {
|
export const SelectOptionColorMap = {
|
||||||
[SelectOptionColorPB.Purple]: 'bg-tint-purple',
|
[SelectOptionColorPB.Purple]: 'bg-tint-purple',
|
@ -0,0 +1 @@
|
|||||||
|
export * from './SelectCell';
|
@ -1,41 +1,39 @@
|
|||||||
import { Popover, TextareaAutosize } from '@mui/material';
|
import { Popover, TextareaAutosize } from '@mui/material';
|
||||||
import { FC, FormEventHandler, useCallback, useEffect, useRef, useState } from 'react';
|
import { FC, FormEventHandler, useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { Database } from '$app/interfaces/database';
|
import { useViewId } from '$app/hooks';
|
||||||
import * as service from '$app/components/database/database_bd_svc';
|
import { cellService, Field, TextCell as TextCellType } from '../../application';
|
||||||
import { useViewId } from '../../database.hooks';
|
|
||||||
import { CellText } from '../../_shared';
|
import { CellText } from '../../_shared';
|
||||||
|
|
||||||
export const GridTextCell: FC<{
|
export const TextCell: FC<{
|
||||||
rowId: string;
|
field: Field,
|
||||||
field: Database.Field,
|
cell: TextCellType;
|
||||||
cell: Database.TextCell | null;
|
}> = ({ field, cell }) => {
|
||||||
}> = ({ rowId, field, cell }) => {
|
|
||||||
const viewId = useViewId();
|
const viewId = useViewId();
|
||||||
|
const cellRef = useRef<HTMLDivElement>(null);
|
||||||
const [ editing, setEditing ] = useState(false);
|
const [ editing, setEditing ] = useState(false);
|
||||||
const [ text, setText ] = useState('');
|
const [ text, setText ] = useState('');
|
||||||
const [ width, setWidth ] = useState<number | undefined>(undefined);
|
const [ width, setWidth ] = useState<number | undefined>(undefined);
|
||||||
const cellRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
if (editing) {
|
if (editing) {
|
||||||
if (text !== cell?.data) {
|
if (text !== cell.data) {
|
||||||
void service.updateCell(viewId, rowId, field.id, text);
|
void cellService.updateCell(viewId, cell.rowId, field.id, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDoubleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
setText(cell?.data ?? '');
|
setText(cell.data);
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
}, [cell?.data]);
|
}, [cell.data]);
|
||||||
|
|
||||||
const handleInput = useCallback<FormEventHandler<HTMLTextAreaElement>>((event) => {
|
const handleInput = useCallback<FormEventHandler<HTMLTextAreaElement>>((event) => {
|
||||||
setText((event.target as HTMLTextAreaElement).value);
|
setText((event.target as HTMLTextAreaElement).value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (cellRef.current) {
|
if (cellRef.current) {
|
||||||
setWidth(cellRef.current.clientWidth);
|
setWidth(cellRef.current.clientWidth);
|
||||||
}
|
}
|
||||||
@ -46,9 +44,9 @@ export const GridTextCell: FC<{
|
|||||||
<CellText
|
<CellText
|
||||||
ref={cellRef}
|
ref={cellRef}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onDoubleClick={handleDoubleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{cell?.data}
|
{cell.data}
|
||||||
</CellText>
|
</CellText>
|
||||||
{editing && (
|
{editing && (
|
||||||
<Popover
|
<Popover
|
@ -0,0 +1 @@
|
|||||||
|
export * from './Cell';
|
@ -0,0 +1,13 @@
|
|||||||
|
import { Sort } from '../../application';
|
||||||
|
import { useDatabase } from '../../Database.hooks';
|
||||||
|
import { Sorts } from '../sort';
|
||||||
|
|
||||||
|
export const DatabaseSettings = () => {
|
||||||
|
const { sorts } = useDatabase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center border-t">
|
||||||
|
<Sorts sorts={sorts as Sort[]} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './DatabaseSettings';
|
@ -0,0 +1,20 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Field as FieldType } from '../../application';
|
||||||
|
import { FieldTypeSvg } from './FieldTypeSvg';
|
||||||
|
|
||||||
|
export interface FieldProps {
|
||||||
|
field: FieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Field: FC<FieldProps> = ({
|
||||||
|
field,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center px-2 w-full">
|
||||||
|
<FieldTypeSvg className="text-base mr-1" type={field.type} />
|
||||||
|
<span className="flex-1 text-left text-xs truncate">
|
||||||
|
{field.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,32 @@
|
|||||||
|
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 { Field } from './Field';
|
||||||
|
|
||||||
|
export interface FieldSelectProps extends Omit<SelectProps, 'onChange'> {
|
||||||
|
onChange?: (event: SelectChangeEvent<unknown>, field: FieldType | undefined) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FieldSelect: FC<FieldSelectProps> = ({
|
||||||
|
onChange,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const { fields } = useDatabase();
|
||||||
|
|
||||||
|
const handleChange = useCallback((event: SelectChangeEvent<unknown>) => {
|
||||||
|
const selectedId = event.target.value;
|
||||||
|
|
||||||
|
onChange?.(event, fields.find(field => field.id === selectedId));
|
||||||
|
}, [onChange, fields]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select onChange={handleChange} {...props}>
|
||||||
|
{fields.map(field => (
|
||||||
|
<MenuItem key={field.id} value={field.id}>
|
||||||
|
<Field field={field} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
import { FC, memo } from 'react';
|
||||||
|
import { FieldType } from '@/services/backend';
|
||||||
|
import { ReactComponent as TextSvg } from '$app/assets/database/field-type-text.svg';
|
||||||
|
import { ReactComponent as NumberSvg } from '$app/assets/database/field-type-number.svg';
|
||||||
|
import { ReactComponent as DateSvg } from '$app/assets/database/field-type-date.svg';
|
||||||
|
import { ReactComponent as SingleSelectSvg } from '$app/assets/database/field-type-single-select.svg';
|
||||||
|
import { ReactComponent as MultiSelectSvg } from '$app/assets/database/field-type-multi-select.svg';
|
||||||
|
import { ReactComponent as ChecklistSvg } from '$app/assets/database/field-type-checklist.svg';
|
||||||
|
import { ReactComponent as CheckboxSvg } from '$app/assets/database/field-type-checkbox.svg';
|
||||||
|
import { ReactComponent as URLSvg } from '$app/assets/database/field-type-url.svg';
|
||||||
|
import { ReactComponent as LastEditedTimeSvg } from '$app/assets/database/field-type-last-edited-time.svg';
|
||||||
|
|
||||||
|
export const FieldTypeSvgMap: Record<FieldType, FC<React.SVGProps<SVGSVGElement>>> = {
|
||||||
|
[FieldType.RichText]: TextSvg,
|
||||||
|
[FieldType.Number]: NumberSvg,
|
||||||
|
[FieldType.DateTime]: DateSvg,
|
||||||
|
[FieldType.SingleSelect]: SingleSelectSvg,
|
||||||
|
[FieldType.MultiSelect]: MultiSelectSvg,
|
||||||
|
[FieldType.Checkbox]: CheckboxSvg,
|
||||||
|
[FieldType.URL]: URLSvg,
|
||||||
|
[FieldType.Checklist]: ChecklistSvg,
|
||||||
|
[FieldType.LastEditedTime]: LastEditedTimeSvg,
|
||||||
|
[FieldType.CreatedTime]: LastEditedTimeSvg,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FieldTypeSvg: FC<{ type: FieldType, className?: string }> = memo(({ type, ...props }) => {
|
||||||
|
const Svg = FieldTypeSvgMap[type];
|
||||||
|
|
||||||
|
return <Svg {...props} />;
|
||||||
|
});
|
@ -0,0 +1,19 @@
|
|||||||
|
import { t } from 'i18next';
|
||||||
|
import { FieldType } from '@/services/backend';
|
||||||
|
|
||||||
|
export const FieldTypeTextMap = {
|
||||||
|
[FieldType.RichText]: 'textFieldName',
|
||||||
|
[FieldType.Number]: 'numberFieldName',
|
||||||
|
[FieldType.DateTime]: 'dateFieldName',
|
||||||
|
[FieldType.SingleSelect]: 'singleSelectFieldName',
|
||||||
|
[FieldType.MultiSelect]: 'multiSelectFieldName',
|
||||||
|
[FieldType.Checkbox]: 'checkboxFieldName',
|
||||||
|
[FieldType.URL]: 'urlFieldName',
|
||||||
|
[FieldType.Checklist]: 'checklistFieldName',
|
||||||
|
[FieldType.LastEditedTime]: 'updatedAtFieldName',
|
||||||
|
[FieldType.CreatedTime]: 'createdAtFieldName',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const FieldTypeText = (type: FieldType) => {
|
||||||
|
return t(`grid.field.${FieldTypeTextMap[type]}`);
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
import { Menu, MenuItem, MenuProps } from '@mui/material';
|
||||||
|
import { FC, MouseEvent } from 'react';
|
||||||
|
import { Field as FieldType } from '../../application';
|
||||||
|
import { useDatabase } from '../../Database.hooks';
|
||||||
|
import { Field } from './Field';
|
||||||
|
|
||||||
|
export interface FieldsMenuProps extends MenuProps {
|
||||||
|
onMenuItemClick?: (event: MouseEvent<HTMLLIElement>, field: FieldType) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FieldsMenu: FC<FieldsMenuProps> = ({ onMenuItemClick, ...props }) => {
|
||||||
|
const { fields } = useDatabase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu {...props}>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<MenuItem
|
||||||
|
key={field.id}
|
||||||
|
value={field.id}
|
||||||
|
onClick={(event) => {
|
||||||
|
onMenuItemClick?.(event, field);
|
||||||
|
props.onClose?.({}, 'backdropClick');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Field field={field} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,3 @@
|
|||||||
|
export * from './Field';
|
||||||
|
export * from './FieldSelect';
|
||||||
|
export * from './FieldsMenu';
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './tab_bar';
|
||||||
|
export * from './cell';
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user