mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: integrate database controller (tauri)
* feat: using controllers in react hooks WIP (#1915) * chore: add edit / create field test * chore: add delete field test * chore: change log class arguments * chore: delete/create row * chore: set tracing log to debug level * fix: filter notification with id * chore: add get single select type option data * fix: high cpu usage * chore: format code * chore: update tokio version * chore: config tokio runtime subscriber * chore: add profiling feature * chore: setup auto login * chore: fix tauri build * chore: (unstable) using controllers * fix: initially authenticated and serializable fix * fix: ci warning * ci: compile error * fix: new folder trash overflow * fix: min width for nav panel * fix: nav panel and main panel animation on hide menu * fix: highlight active page * fix: post merge fixes * fix: post merge fix * fix: remove warnings * fix: change IDatabaseField fix eslint errors * chore: create cell component for each field type * chore: move cell hook into custom cell component * chore: refactor row hook * chore: add tauri clean --------- Co-authored-by: nathan <nathan@appflowy.io> Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com> * ci: fix wanrings --------- Co-authored-by: Askarbek Zadauly <ascarbek@gmail.com>
This commit is contained in:
parent
e73870e6e2
commit
90da54d12f
@ -9,7 +9,9 @@
|
|||||||
"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": "eslint --quiet --ext .js,.ts,.tsx .",
|
||||||
"test:prettier": "yarn prettier --list-different src",
|
"test:prettier": "yarn prettier --list-different src",
|
||||||
|
"tauri:clean": "cargo make --cwd .. tauri_clean",
|
||||||
"tauri:dev": "tauri dev",
|
"tauri:dev": "tauri dev",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,7 @@ export const useDatabase = () => {
|
|||||||
const database = useAppSelector((state) => state.database);
|
const database = useAppSelector((state) => state.database);
|
||||||
|
|
||||||
const newField = () => {
|
const newField = () => {
|
||||||
dispatch(
|
/* dispatch(
|
||||||
databaseActions.addField({
|
databaseActions.addField({
|
||||||
field: {
|
field: {
|
||||||
fieldId: nanoid(8),
|
fieldId: nanoid(8),
|
||||||
@ -18,22 +18,25 @@ export const useDatabase = () => {
|
|||||||
title: 'new field',
|
title: 'new field',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);*/
|
||||||
|
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;
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
databaseActions.updateField({
|
databaseActions.updateField({
|
||||||
field,
|
field,
|
||||||
})
|
})
|
||||||
);
|
);*/
|
||||||
|
console.log('depreciated');
|
||||||
};
|
};
|
||||||
|
|
||||||
const newRow = () => {
|
const newRow = () => {
|
||||||
dispatch(databaseActions.addRow());
|
// dispatch(databaseActions.addRow());
|
||||||
|
console.log('depreciated');
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
import { TypeOptionController } from '../../../stores/effects/database/field/type_option/type_option_controller';
|
||||||
|
import { Some } from 'ts-results';
|
||||||
|
import { IDatabaseField, ISelectOption } from '../../../stores/reducers/database/slice';
|
||||||
|
import {
|
||||||
|
ChecklistTypeOptionPB,
|
||||||
|
DateFormat,
|
||||||
|
FieldType,
|
||||||
|
MultiSelectTypeOptionPB,
|
||||||
|
NumberFormat,
|
||||||
|
SingleSelectTypeOptionPB,
|
||||||
|
TimeFormat,
|
||||||
|
} from '../../../../services/backend';
|
||||||
|
import {
|
||||||
|
makeChecklistTypeOptionContext,
|
||||||
|
makeDateTypeOptionContext,
|
||||||
|
makeMultiSelectTypeOptionContext,
|
||||||
|
makeNumberTypeOptionContext,
|
||||||
|
makeSingleSelectTypeOptionContext,
|
||||||
|
} from '../../../stores/effects/database/field/type_option/type_option_context';
|
||||||
|
import { boardActions } from '../../../stores/reducers/board/slice';
|
||||||
|
import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
|
||||||
|
import { AppDispatch } from '../../../stores/store';
|
||||||
|
|
||||||
|
export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?: AppDispatch): Promise<IDatabaseField> {
|
||||||
|
const field = fieldInfo.field;
|
||||||
|
const typeOptionController = new TypeOptionController(viewId, Some(fieldInfo));
|
||||||
|
|
||||||
|
// temporary hack to set grouping field
|
||||||
|
let groupingFieldSelected = false;
|
||||||
|
|
||||||
|
switch (field.field_type) {
|
||||||
|
case FieldType.SingleSelect:
|
||||||
|
case FieldType.MultiSelect:
|
||||||
|
case FieldType.Checklist: {
|
||||||
|
let selectOptions: ISelectOption[] = [];
|
||||||
|
let typeOption: SingleSelectTypeOptionPB | MultiSelectTypeOptionPB | ChecklistTypeOptionPB | undefined;
|
||||||
|
|
||||||
|
if (field.field_type === FieldType.SingleSelect) {
|
||||||
|
typeOption = (await makeSingleSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||||
|
if (!groupingFieldSelected) {
|
||||||
|
if (dispatch) {
|
||||||
|
dispatch(boardActions.setGroupingFieldId({ fieldId: field.id }));
|
||||||
|
}
|
||||||
|
groupingFieldSelected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (field.field_type === FieldType.MultiSelect) {
|
||||||
|
typeOption = (await makeMultiSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||||
|
}
|
||||||
|
if (field.field_type === FieldType.Checklist) {
|
||||||
|
typeOption = (await makeChecklistTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeOption) {
|
||||||
|
selectOptions = typeOption.options.map<ISelectOption>((option) => {
|
||||||
|
return {
|
||||||
|
selectOptionId: option.id,
|
||||||
|
title: option.name,
|
||||||
|
color: option.color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fieldId: field.id,
|
||||||
|
title: field.name,
|
||||||
|
fieldType: field.field_type,
|
||||||
|
fieldOptions: {
|
||||||
|
selectOptions,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case FieldType.Number: {
|
||||||
|
const typeOption = (await makeNumberTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||||
|
return {
|
||||||
|
fieldId: field.id,
|
||||||
|
title: field.name,
|
||||||
|
fieldType: field.field_type,
|
||||||
|
fieldOptions: {
|
||||||
|
numberFormat: typeOption.format,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case FieldType.DateTime: {
|
||||||
|
const typeOption = (await makeDateTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||||
|
return {
|
||||||
|
fieldId: field.id,
|
||||||
|
title: field.name,
|
||||||
|
fieldType: field.field_type,
|
||||||
|
fieldOptions: {
|
||||||
|
dateFormat: typeOption.date_format,
|
||||||
|
timeFormat: typeOption.time_format,
|
||||||
|
includeTime: typeOption.include_time,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return {
|
||||||
|
fieldId: field.id,
|
||||||
|
title: field.name,
|
||||||
|
fieldType: field.field_type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { CellCache } from '../../../stores/effects/database/cell/cell_cache';
|
||||||
|
import { FieldController } from '../../../stores/effects/database/field/field_controller';
|
||||||
|
import { CellControllerBuilder } from '../../../stores/effects/database/cell/controller_builder';
|
||||||
|
import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '../../../../services/backend';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => {
|
||||||
|
const [data, setData] = useState<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
|
||||||
|
const cellController = builder.build();
|
||||||
|
cellController.subscribeChanged({
|
||||||
|
onCellChanged: (value) => {
|
||||||
|
setData(value.unwrap());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ignore the return value, because we are using the subscription
|
||||||
|
void cellController.getCellData();
|
||||||
|
|
||||||
|
// dispose the cell controller when the component is unmounted
|
||||||
|
return () => {
|
||||||
|
void cellController.dispose();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,68 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { DatabaseController } from '../../../stores/effects/database/database_controller';
|
||||||
|
import {
|
||||||
|
databaseActions,
|
||||||
|
DatabaseFieldMap,
|
||||||
|
IDatabaseColumn,
|
||||||
|
IDatabaseRow,
|
||||||
|
} from '../../../stores/reducers/database/slice';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
||||||
|
import loadField from './loadField';
|
||||||
|
import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
|
||||||
|
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
|
||||||
|
|
||||||
|
export const useDatabase = (viewId: string) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
const boardStore = useAppSelector((state) => state.board);
|
||||||
|
const [controller, setController] = useState<DatabaseController>();
|
||||||
|
const [rows, setRows] = useState<readonly RowInfo[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!viewId.length) return;
|
||||||
|
const c = new DatabaseController(viewId);
|
||||||
|
setController(c);
|
||||||
|
|
||||||
|
// on unmount dispose the controller
|
||||||
|
return () => void c.dispose();
|
||||||
|
}, [viewId]);
|
||||||
|
|
||||||
|
const loadFields = async (fieldInfos: readonly FieldInfo[]) => {
|
||||||
|
const fields: DatabaseFieldMap = {};
|
||||||
|
const columns: IDatabaseColumn[] = [];
|
||||||
|
|
||||||
|
for (const fieldInfo of fieldInfos) {
|
||||||
|
const fieldPB = fieldInfo.field;
|
||||||
|
columns.push({
|
||||||
|
fieldId: fieldPB.id,
|
||||||
|
sort: 'none',
|
||||||
|
visible: fieldPB.visibility,
|
||||||
|
});
|
||||||
|
|
||||||
|
const field = await loadField(viewId, fieldInfo, dispatch);
|
||||||
|
fields[field.fieldId] = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(databaseActions.updateFields({ fields }));
|
||||||
|
dispatch(databaseActions.updateColumns({ columns }));
|
||||||
|
console.log(fields, columns);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!controller) return;
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
controller.subscribe({
|
||||||
|
onRowsChanged: (rowInfos) => {
|
||||||
|
setRows(rowInfos);
|
||||||
|
},
|
||||||
|
onFieldsChanged: (fieldInfos) => {
|
||||||
|
void loadFields(fieldInfos);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await controller.open();
|
||||||
|
})();
|
||||||
|
}, [controller]);
|
||||||
|
|
||||||
|
return { loadFields, controller, rows };
|
||||||
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
import { DatabaseController } from '../../../stores/effects/database/database_controller';
|
||||||
|
import { RowController } from '../../../stores/effects/database/row/row_controller';
|
||||||
|
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
|
||||||
|
import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const useRow = (viewId: string, databaseController: DatabaseController, rowInfo: RowInfo) => {
|
||||||
|
const [cells, setCells] = useState<{ fieldId: string; cellIdentifier: CellIdentifier }[]>([]);
|
||||||
|
const [rowController, setRowController] = useState<RowController>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const rowCache = databaseController.databaseViewCache.getRowCache();
|
||||||
|
const fieldController = databaseController.fieldController;
|
||||||
|
const c = new RowController(rowInfo, fieldController, rowCache);
|
||||||
|
setRowController(c);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// dispose row controller in future
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!rowController) return;
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
const cellsPB = await rowController.loadCells();
|
||||||
|
const loadingCells: { fieldId: string; cellIdentifier: CellIdentifier }[] = [];
|
||||||
|
|
||||||
|
for (const [fieldId, cellIdentifier] of cellsPB.entries()) {
|
||||||
|
loadingCells.push({
|
||||||
|
fieldId,
|
||||||
|
cellIdentifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setCells(loadingCells);
|
||||||
|
})();
|
||||||
|
}, [rowController]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
cells: cells,
|
||||||
|
};
|
||||||
|
};
|
@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { GetStarted } from './GetStarted/GetStarted';
|
import { GetStarted } from './GetStarted/GetStarted';
|
||||||
import { AppflowyLogo } from '../_shared/svg/AppflowyLogo';
|
import { AppflowyLogo } from '../_shared/svg/AppflowyLogo';
|
||||||
|
|
||||||
|
|
||||||
export const ProtectedRoutes = () => {
|
export const ProtectedRoutes = () => {
|
||||||
const { currentUser, checkUser } = useAuth();
|
const { currentUser, checkUser } = useAuth();
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
@ -1,34 +1,24 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useAppDispatch, useAppSelector } from '../../stores/store';
|
import { useAppDispatch, useAppSelector } from '../../stores/store';
|
||||||
import { boardActions } from '../../stores/reducers/board/slice';
|
import { boardActions } from '../../stores/reducers/board/slice';
|
||||||
import { ICellData, IDatabase, IDatabaseRow, ISelectOption } from '../../stores/reducers/database/slice';
|
import { ISelectOption, ISelectOptionType } from '../../stores/reducers/database/slice';
|
||||||
|
|
||||||
export const useBoard = () => {
|
export const useBoard = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const groupingFieldId = useAppSelector((state) => state.board);
|
const groupingFieldId = useAppSelector((state) => state.board);
|
||||||
const database = useAppSelector((state) => state.database);
|
const database = useAppSelector((state) => state.database);
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [boardColumns, setBoardColumns] =
|
const [boardColumns, setBoardColumns] = useState<ISelectOption[]>([]);
|
||||||
useState<(ISelectOption & { rows: (IDatabaseRow & { isGhost: boolean })[] })[]>();
|
|
||||||
const [movingRowId, setMovingRowId] = useState<string | undefined>(undefined);
|
const [movingRowId, setMovingRowId] = useState<string | undefined>(undefined);
|
||||||
const [ghostLocation, setGhostLocation] = useState<{ column: number; row: number }>({ column: 0, row: 0 });
|
const [ghostLocation, setGhostLocation] = useState<{ column: number; row: number }>({ column: 0, row: 0 });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(database.title);
|
setTitle(database.title);
|
||||||
setBoardColumns(
|
if (database.fields[groupingFieldId]) {
|
||||||
database.fields[groupingFieldId].fieldOptions.selectOptions?.map((groupFieldItem) => {
|
setBoardColumns(
|
||||||
const rows = database.rows
|
(database.fields[groupingFieldId].fieldOptions as ISelectOptionType | undefined)?.selectOptions || []
|
||||||
.filter((row) => row.cells[groupingFieldId].optionIds?.some((so) => so === groupFieldItem.selectOptionId))
|
);
|
||||||
.map((row) => ({
|
}
|
||||||
...row,
|
|
||||||
isGhost: false,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
...groupFieldItem,
|
|
||||||
rows: rows,
|
|
||||||
};
|
|
||||||
}) || []
|
|
||||||
);
|
|
||||||
}, [database, groupingFieldId]);
|
}, [database, groupingFieldId]);
|
||||||
|
|
||||||
const changeGroupingField = (fieldId: string) => {
|
const changeGroupingField = (fieldId: string) => {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { SettingsSvg } from '../_shared/svg/SettingsSvg';
|
import { SettingsSvg } from '../_shared/svg/SettingsSvg';
|
||||||
import { SearchInput } from '../_shared/SearchInput';
|
import { SearchInput } from '../_shared/SearchInput';
|
||||||
import { useDatabase } from '../_shared/Database.hooks';
|
|
||||||
import { BoardBlock } from './BoardBlock';
|
import { BoardBlock } from './BoardBlock';
|
||||||
import { NewBoardBlock } from './NewBoardBlock';
|
import { NewBoardBlock } from './NewBoardBlock';
|
||||||
import { IDatabaseRow } from '../../stores/reducers/database/slice';
|
|
||||||
import { useBoard } from './Board.hooks';
|
import { useBoard } from './Board.hooks';
|
||||||
|
import { useDatabase } from '../_shared/database-hooks/useDatabase';
|
||||||
|
|
||||||
|
export const Board = ({ viewId }: { viewId: string }) => {
|
||||||
|
const { controller, rows } = useDatabase(viewId);
|
||||||
|
|
||||||
export const Board = () => {
|
|
||||||
const { database, newField, renameField, newRow } = useDatabase();
|
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
boardColumns,
|
boardColumns,
|
||||||
@ -36,16 +36,15 @@ export const Board = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={'relative w-full flex-1 overflow-auto'}>
|
<div className={'relative w-full flex-1 overflow-auto'}>
|
||||||
<div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
|
<div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
|
||||||
{database &&
|
{controller &&
|
||||||
boardColumns?.map((column, index) => (
|
boardColumns?.map((column, index) => (
|
||||||
<BoardBlock
|
<BoardBlock
|
||||||
|
viewId={viewId}
|
||||||
|
controller={controller}
|
||||||
key={index}
|
key={index}
|
||||||
title={column.title}
|
title={column.title}
|
||||||
|
rows={rows}
|
||||||
groupingFieldId={groupingFieldId}
|
groupingFieldId={groupingFieldId}
|
||||||
count={column.rows.length}
|
|
||||||
fields={database.fields}
|
|
||||||
columns={database.columns}
|
|
||||||
rows={column.rows}
|
|
||||||
startMove={startMove}
|
startMove={startMove}
|
||||||
endMove={endMove}
|
endMove={endMove}
|
||||||
/>
|
/>
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
import { Details2Svg } from '../_shared/svg/Details2Svg';
|
import { Details2Svg } from '../_shared/svg/Details2Svg';
|
||||||
import AddSvg from '../_shared/svg/AddSvg';
|
import AddSvg from '../_shared/svg/AddSvg';
|
||||||
import { DatabaseFieldMap, ICellData, IDatabaseColumn, IDatabaseRow } from '../../stores/reducers/database/slice';
|
import { BoardCard } from './BoardCard';
|
||||||
import { BoardBlockItem } from './BoardBlockItem';
|
import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
||||||
|
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
||||||
|
|
||||||
export const BoardBlock = ({
|
export const BoardBlock = ({
|
||||||
|
viewId,
|
||||||
|
controller,
|
||||||
title,
|
title,
|
||||||
groupingFieldId,
|
groupingFieldId,
|
||||||
count,
|
|
||||||
fields,
|
|
||||||
columns,
|
|
||||||
rows,
|
rows,
|
||||||
startMove,
|
startMove,
|
||||||
endMove,
|
endMove,
|
||||||
}: {
|
}: {
|
||||||
|
viewId: string;
|
||||||
|
controller: DatabaseController;
|
||||||
title: string;
|
title: string;
|
||||||
groupingFieldId: string;
|
groupingFieldId: string;
|
||||||
count: number;
|
rows: readonly RowInfo[];
|
||||||
fields: DatabaseFieldMap;
|
|
||||||
columns: IDatabaseColumn[];
|
|
||||||
rows: IDatabaseRow[];
|
|
||||||
startMove: (id: string) => void;
|
startMove: (id: string) => void;
|
||||||
endMove: () => void;
|
endMove: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
@ -27,7 +26,7 @@ export const BoardBlock = ({
|
|||||||
<div className={'flex items-center justify-between p-4'}>
|
<div className={'flex items-center justify-between p-4'}>
|
||||||
<div className={'flex items-center gap-2'}>
|
<div className={'flex items-center gap-2'}>
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
<span className={'text-shade-4'}>({count})</span>
|
<span className={'text-shade-4'}>()</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex items-center gap-2'}>
|
<div className={'flex items-center gap-2'}>
|
||||||
<button className={'h-5 w-5 rounded hover:bg-surface-2'}>
|
<button className={'h-5 w-5 rounded hover:bg-surface-2'}>
|
||||||
@ -40,15 +39,15 @@ export const BoardBlock = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}>
|
<div className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}>
|
||||||
{rows.map((row, index) => (
|
{rows.map((row, index) => (
|
||||||
<BoardBlockItem
|
<BoardCard
|
||||||
|
viewId={viewId}
|
||||||
|
controller={controller}
|
||||||
key={index}
|
key={index}
|
||||||
groupingFieldId={groupingFieldId}
|
groupingFieldId={groupingFieldId}
|
||||||
fields={fields}
|
|
||||||
columns={columns}
|
|
||||||
row={row}
|
row={row}
|
||||||
startMove={() => startMove(row.rowId)}
|
startMove={() => startMove(row.row.id)}
|
||||||
endMove={() => endMove()}
|
endMove={() => endMove()}
|
||||||
></BoardBlockItem>
|
></BoardCard>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={'p-2'}>
|
<div className={'p-2'}>
|
||||||
|
@ -3,22 +3,34 @@ import { Details2Svg } from '../_shared/svg/Details2Svg';
|
|||||||
import { FieldType } from '../../../services/backend';
|
import { FieldType } from '../../../services/backend';
|
||||||
import { getBgColor } from '../_shared/getColor';
|
import { getBgColor } from '../_shared/getColor';
|
||||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react';
|
import { MouseEventHandler, useEffect, useRef, useState } from 'react';
|
||||||
|
import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
||||||
|
import { useRow } from '../_shared/database-hooks/useRow';
|
||||||
|
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
||||||
|
import { useAppSelector } from '../../stores/store';
|
||||||
|
import { BoardCell } from './BoardCell';
|
||||||
|
|
||||||
export const BoardBlockItem = ({
|
export const BoardCard = ({
|
||||||
|
viewId,
|
||||||
|
controller,
|
||||||
groupingFieldId,
|
groupingFieldId,
|
||||||
fields,
|
// fields,
|
||||||
columns,
|
// columns,
|
||||||
row,
|
row,
|
||||||
startMove,
|
startMove,
|
||||||
endMove,
|
endMove,
|
||||||
}: {
|
}: {
|
||||||
|
viewId: string;
|
||||||
|
controller: DatabaseController;
|
||||||
groupingFieldId: string;
|
groupingFieldId: string;
|
||||||
fields: DatabaseFieldMap;
|
// fields: DatabaseFieldMap;
|
||||||
columns: IDatabaseColumn[];
|
// columns: IDatabaseColumn[];
|
||||||
row: IDatabaseRow;
|
row: RowInfo;
|
||||||
startMove: () => void;
|
startMove: () => void;
|
||||||
endMove: () => void;
|
endMove: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { cells } = useRow(viewId, controller, row);
|
||||||
|
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
const [isMoving, setIsMoving] = useState(false);
|
const [isMoving, setIsMoving] = useState(false);
|
||||||
const [isDown, setIsDown] = useState(false);
|
const [isDown, setIsDown] = useState(false);
|
||||||
const [ghostWidth, setGhostWidth] = useState(0);
|
const [ghostWidth, setGhostWidth] = useState(0);
|
||||||
@ -26,6 +38,7 @@ export const BoardBlockItem = ({
|
|||||||
const [ghostLeft, setGhostLeft] = useState(0);
|
const [ghostLeft, setGhostLeft] = useState(0);
|
||||||
const [ghostTop, setGhostTop] = useState(0);
|
const [ghostTop, setGhostTop] = useState(0);
|
||||||
const el = useRef<HTMLDivElement>(null);
|
const el = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (el.current?.getBoundingClientRect && isMoving) {
|
if (el.current?.getBoundingClientRect && isMoving) {
|
||||||
const { left, top, width, height } = el.current.getBoundingClientRect();
|
const { left, top, width, height } = el.current.getBoundingClientRect();
|
||||||
@ -74,31 +87,14 @@ export const BoardBlockItem = ({
|
|||||||
<Details2Svg></Details2Svg>
|
<Details2Svg></Details2Svg>
|
||||||
</button>
|
</button>
|
||||||
<div className={'flex flex-col gap-3'}>
|
<div className={'flex flex-col gap-3'}>
|
||||||
{columns
|
{cells.map((cell, index) => (
|
||||||
.filter((column) => column.fieldId !== groupingFieldId)
|
<BoardCell
|
||||||
.map((column, index) => {
|
key={index}
|
||||||
switch (fields[column.fieldId].fieldType) {
|
cellIdentifier={cell.cellIdentifier}
|
||||||
case FieldType.MultiSelect:
|
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||||
return (
|
fieldController={controller.fieldController}
|
||||||
<div key={index} className={'flex flex-wrap items-center gap-2'}>
|
></BoardCell>
|
||||||
{row.cells[column.fieldId].optionIds?.map((option, indexOption) => {
|
))}
|
||||||
const selectOptions = fields[column.fieldId].fieldOptions.selectOptions;
|
|
||||||
const selectedOption = selectOptions?.find((so) => so.selectOptionId === option);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={indexOption}
|
|
||||||
className={`rounded px-1 py-0.5 text-sm ${getBgColor(selectedOption?.color)}`}
|
|
||||||
>
|
|
||||||
{selectedOption?.title}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return <div key={index}>{row.cells[column.fieldId].data}</div>;
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isMoving && (
|
{isMoving && (
|
@ -0,0 +1,43 @@
|
|||||||
|
import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { CellCache } from '../../stores/effects/database/cell/cell_cache';
|
||||||
|
import { FieldController } from '../../stores/effects/database/field/field_controller';
|
||||||
|
import { FieldType } from '../../../services/backend';
|
||||||
|
import { BoardOptionsCell } from './BoardOptionsCell';
|
||||||
|
import { BoardDateCell } from './BoardDateCell';
|
||||||
|
import { BoardTextCell } from './BoardTextCell';
|
||||||
|
|
||||||
|
export const BoardCell = ({
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
}: {
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{cellIdentifier.fieldType === FieldType.SingleSelect ||
|
||||||
|
cellIdentifier.fieldType === FieldType.MultiSelect ||
|
||||||
|
cellIdentifier.fieldType === FieldType.Checklist ? (
|
||||||
|
<BoardOptionsCell
|
||||||
|
cellIdentifier={cellIdentifier}
|
||||||
|
cellCache={cellCache}
|
||||||
|
fieldController={fieldController}
|
||||||
|
></BoardOptionsCell>
|
||||||
|
) : cellIdentifier.fieldType === FieldType.DateTime ? (
|
||||||
|
<BoardDateCell
|
||||||
|
cellIdentifier={cellIdentifier}
|
||||||
|
cellCache={cellCache}
|
||||||
|
fieldController={fieldController}
|
||||||
|
></BoardDateCell>
|
||||||
|
) : (
|
||||||
|
<BoardTextCell
|
||||||
|
cellIdentifier={cellIdentifier}
|
||||||
|
cellCache={cellCache}
|
||||||
|
fieldController={fieldController}
|
||||||
|
></BoardTextCell>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
import { DateCellDataPB } from '../../../services/backend';
|
||||||
|
import { useCell } from '../_shared/database-hooks/useCell';
|
||||||
|
import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { CellCache } from '../../stores/effects/database/cell/cell_cache';
|
||||||
|
import { FieldController } from '../../stores/effects/database/field/field_controller';
|
||||||
|
|
||||||
|
export const BoardDateCell = ({
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
}: {
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
}) => {
|
||||||
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
return <div>{(data as DateCellDataPB | undefined)?.date || ''}</div>;
|
||||||
|
};
|
@ -0,0 +1,25 @@
|
|||||||
|
import { SelectOptionCellDataPB } from '../../../services/backend';
|
||||||
|
import { useCell } from '../_shared/database-hooks/useCell';
|
||||||
|
import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { CellCache } from '../../stores/effects/database/cell/cell_cache';
|
||||||
|
import { FieldController } from '../../stores/effects/database/field/field_controller';
|
||||||
|
|
||||||
|
export const BoardOptionsCell = ({
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
}: {
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
}) => {
|
||||||
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(data as SelectOptionCellDataPB | undefined)?.select_options?.map((option, index) => (
|
||||||
|
<div key={index}>{option?.name || ''}</div>
|
||||||
|
)) || ''}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { CellCache } from '../../stores/effects/database/cell/cell_cache';
|
||||||
|
import { FieldController } from '../../stores/effects/database/field/field_controller';
|
||||||
|
import { useCell } from '../_shared/database-hooks/useCell';
|
||||||
|
|
||||||
|
export const BoardTextCell = ({
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
}: {
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
}) => {
|
||||||
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
|
||||||
|
return <div>{(data as string | undefined) || ''}</div>;
|
||||||
|
};
|
@ -1,7 +1,15 @@
|
|||||||
export const Breadcrumbs = () => {
|
import { ShowMenuSvg } from '../../_shared/svg/ShowMenuSvg';
|
||||||
|
|
||||||
|
export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boolean; onShowMenuClick: () => void }) => {
|
||||||
return (
|
return (
|
||||||
<div className={'flex items-center'}>
|
<div className={'flex items-center'}>
|
||||||
<div className={'mr-4 flex items-center'}>
|
<div className={'mr-4 flex items-center'}>
|
||||||
|
{menuHidden && (
|
||||||
|
<button onClick={() => onShowMenuClick()} className={'mr-2 h-5 w-5'}>
|
||||||
|
<ShowMenuSvg></ShowMenuSvg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<button className={'p-1'} onClick={() => history.back()}>
|
<button className={'p-1'} onClick={() => history.back()}>
|
||||||
<img src={'/images/home/arrow_left.svg'} />
|
<img src={'/images/home/arrow_left.svg'} />
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Breadcrumbs } from './Breadcrumbs';
|
import { Breadcrumbs } from './Breadcrumbs';
|
||||||
import { PageOptions } from './PageOptions';
|
import { PageOptions } from './PageOptions';
|
||||||
|
|
||||||
export const HeaderPanel = () => {
|
export const HeaderPanel = ({ menuHidden, onShowMenuClick }: { menuHidden: boolean; onShowMenuClick: () => void }) => {
|
||||||
return (
|
return (
|
||||||
<div className={'flex h-[60px] items-center justify-between border-b border-shade-6 px-8'}>
|
<div className={'flex h-[60px] items-center justify-between border-b border-shade-6 px-8'}>
|
||||||
<Breadcrumbs></Breadcrumbs>
|
<Breadcrumbs menuHidden={menuHidden} onShowMenuClick={onShowMenuClick}></Breadcrumbs>
|
||||||
<PageOptions></PageOptions>
|
<PageOptions></PageOptions>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,40 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode, useEffect, useState } from 'react';
|
||||||
import { HeaderPanel } from './HeaderPanel/HeaderPanel';
|
import { HeaderPanel } from './HeaderPanel/HeaderPanel';
|
||||||
import { FooterPanel } from './FooterPanel';
|
import { FooterPanel } from './FooterPanel';
|
||||||
|
|
||||||
export const MainPanel = ({ children }: { children: ReactNode }) => {
|
const ANIMATION_DURATION = 300;
|
||||||
|
|
||||||
|
export const MainPanel = ({
|
||||||
|
left,
|
||||||
|
menuHidden,
|
||||||
|
onShowMenuClick,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
left: number;
|
||||||
|
menuHidden: boolean;
|
||||||
|
onShowMenuClick: () => void;
|
||||||
|
children: ReactNode;
|
||||||
|
}) => {
|
||||||
|
const [animation, setAnimation] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!menuHidden) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setAnimation(false);
|
||||||
|
}, ANIMATION_DURATION);
|
||||||
|
} else {
|
||||||
|
setAnimation(true);
|
||||||
|
}
|
||||||
|
}, [menuHidden]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex h-full flex-1 flex-col'}>
|
<div
|
||||||
<HeaderPanel></HeaderPanel>
|
className={`absolute inset-0 flex h-full flex-1 flex-col`}
|
||||||
|
style={{
|
||||||
|
transition: menuHidden || animation ? `left ${ANIMATION_DURATION}ms ease-out` : 'none',
|
||||||
|
left: `${menuHidden ? 0 : left}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeaderPanel menuHidden={menuHidden} onShowMenuClick={onShowMenuClick}></HeaderPanel>
|
||||||
<div className={'min-h-0 flex-1 overflow-auto'}>{children}</div>
|
<div className={'min-h-0 flex-1 overflow-auto'}>{children}</div>
|
||||||
<FooterPanel></FooterPanel>
|
<FooterPanel></FooterPanel>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import { useAppSelector } from '../../../stores/store';
|
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { IPage } from '../../../stores/reducers/pages/slice';
|
import { IPage } from '../../../stores/reducers/pages/slice';
|
||||||
import { ViewLayoutTypePB } from '../../../../services/backend';
|
import { ViewLayoutTypePB } from '../../../../services/backend';
|
||||||
import { MouseEventHandler, useState } from 'react';
|
import { MouseEventHandler, useState } from 'react';
|
||||||
|
import { activePageIdActions } from '../../../stores/reducers/activePageId/slice';
|
||||||
|
|
||||||
// number of pixels from left side of screen to show hidden navigation panel
|
// number of pixels from left side of screen to show hidden navigation panel
|
||||||
const FLOATING_PANEL_SHOW_WIDTH = 10;
|
const FLOATING_PANEL_SHOW_WIDTH = 10;
|
||||||
const FLOATING_PANEL_HIDE_EXTRA_WIDTH = 10;
|
const FLOATING_PANEL_HIDE_EXTRA_WIDTH = 10;
|
||||||
|
|
||||||
export const useNavigationPanelHooks = function () {
|
export const useNavigationPanelHooks = function () {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const folders = useAppSelector((state) => state.folders);
|
const folders = useAppSelector((state) => state.folders);
|
||||||
const pages = useAppSelector((state) => state.pages);
|
const pages = useAppSelector((state) => state.pages);
|
||||||
const width = useAppSelector((state) => state.navigationWidth);
|
const width = useAppSelector((state) => state.navigationWidth);
|
||||||
const [navigationPanelFixed, setNavigationPanelFixed] = useState(true);
|
const [navigationPanelFixed, setNavigationPanelFixed] = useState(true);
|
||||||
const [slideInFloatingPanel, setSlideInFloatingPanel] = useState(true);
|
const [slideInFloatingPanel, setSlideInFloatingPanel] = useState(true);
|
||||||
|
const [menuHidden, setMenuHidden] = useState(false);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -28,6 +31,14 @@ export const useNavigationPanelHooks = function () {
|
|||||||
|
|
||||||
const [floatingPanelWidth, setFloatingPanelWidth] = useState(0);
|
const [floatingPanelWidth, setFloatingPanelWidth] = useState(0);
|
||||||
|
|
||||||
|
const onHideMenuClick = () => {
|
||||||
|
setMenuHidden(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onShowMenuClick = () => {
|
||||||
|
setMenuHidden(false);
|
||||||
|
};
|
||||||
|
|
||||||
const onPageClick = (page: IPage) => {
|
const onPageClick = (page: IPage) => {
|
||||||
let pageTypeRoute = (() => {
|
let pageTypeRoute = (() => {
|
||||||
switch (page.pageType) {
|
switch (page.pageType) {
|
||||||
@ -43,6 +54,8 @@ export const useNavigationPanelHooks = function () {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
dispatch(activePageIdActions.setActivePageId(page.id));
|
||||||
|
|
||||||
navigate(`/page/${pageTypeRoute}/${page.id}`);
|
navigate(`/page/${pageTypeRoute}/${page.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,5 +79,8 @@ export const useNavigationPanelHooks = function () {
|
|||||||
onScreenMouseMove,
|
onScreenMouseMove,
|
||||||
slideInFloatingPanel,
|
slideInFloatingPanel,
|
||||||
setFloatingPanelWidth,
|
setFloatingPanelWidth,
|
||||||
|
menuHidden,
|
||||||
|
onHideMenuClick,
|
||||||
|
onShowMenuClick,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -9,14 +9,19 @@ import { IPage } from '../../../stores/reducers/pages/slice';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
const MINIMUM_WIDTH = 200;
|
||||||
|
const ANIMATION_DURATION = 300;
|
||||||
|
|
||||||
export const NavigationPanel = ({
|
export const NavigationPanel = ({
|
||||||
onCollapseNavigationClick,
|
onHideMenuClick,
|
||||||
|
menuHidden,
|
||||||
width,
|
width,
|
||||||
folders,
|
folders,
|
||||||
pages,
|
pages,
|
||||||
onPageClick,
|
onPageClick,
|
||||||
}: {
|
}: {
|
||||||
onCollapseNavigationClick: () => void;
|
onHideMenuClick: () => void;
|
||||||
|
menuHidden: boolean;
|
||||||
width: number;
|
width: number;
|
||||||
folders: IFolder[];
|
folders: IFolder[];
|
||||||
pages: IPage[];
|
pages: IPage[];
|
||||||
@ -24,9 +29,16 @@ export const NavigationPanel = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={'flex flex-col justify-between bg-surface-1 text-sm'} style={{ width: `${width}px` }}>
|
<div
|
||||||
|
className={`absolute inset-0 flex flex-col justify-between bg-surface-1 text-sm`}
|
||||||
|
style={{
|
||||||
|
transition: `left ${ANIMATION_DURATION}ms ease-out`,
|
||||||
|
width: `${width}px`,
|
||||||
|
left: `${menuHidden ? -width : 0}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className={'flex flex-col'}>
|
<div className={'flex flex-col'}>
|
||||||
<AppLogo iconToShow={'hide'} onHideMenuClick={onCollapseNavigationClick}></AppLogo>
|
<AppLogo iconToShow={'hide'} onHideMenuClick={onHideMenuClick}></AppLogo>
|
||||||
<WorkspaceUser></WorkspaceUser>
|
<WorkspaceUser></WorkspaceUser>
|
||||||
<WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
|
<WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
|
||||||
</div>
|
</div>
|
||||||
@ -46,7 +58,7 @@ export const NavigationPanel = ({
|
|||||||
<NewFolderButton></NewFolderButton>
|
<NewFolderButton></NewFolderButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NavigationResizer></NavigationResizer>
|
<NavigationResizer minWidth={MINIMUM_WIDTH}></NavigationResizer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -58,7 +70,7 @@ type AppsContext = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const WorkspaceApps: React.FC<AppsContext> = ({ folders, pages, onPageClick }) => (
|
const WorkspaceApps: React.FC<AppsContext> = ({ folders, pages, onPageClick }) => (
|
||||||
<div className={'flex flex-col px-2'}>
|
<div className={'flex flex-col overflow-auto px-2'} style={{ height: 'calc(100vh - 300px)' }}>
|
||||||
{folders.map((folder, index) => (
|
{folders.map((folder, index) => (
|
||||||
<FolderItem
|
<FolderItem
|
||||||
key={index}
|
key={index}
|
||||||
|
@ -3,13 +3,17 @@ import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { navigationWidthActions } from '../../../stores/reducers/navigation-width/slice';
|
import { navigationWidthActions } from '../../../stores/reducers/navigation-width/slice';
|
||||||
|
|
||||||
export const NavigationResizer = () => {
|
export const NavigationResizer = ({ minWidth }: { minWidth: number }) => {
|
||||||
const width = useAppSelector((state) => state.navigationWidth);
|
const width = useAppSelector((state) => state.navigationWidth);
|
||||||
const appDispatch = useAppDispatch();
|
const appDispatch = useAppDispatch();
|
||||||
const { onMouseDown, movementX } = useResizer();
|
const { onMouseDown, movementX } = useResizer();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
appDispatch(navigationWidthActions.changeWidth(width + movementX));
|
if (width + movementX < minWidth) {
|
||||||
|
appDispatch(navigationWidthActions.changeWidth(minWidth));
|
||||||
|
} else {
|
||||||
|
appDispatch(navigationWidthActions.changeWidth(width + movementX));
|
||||||
|
}
|
||||||
}, [movementX]);
|
}, [movementX]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
|
import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
|
||||||
import { useAppDispatch } from '../../../stores/store';
|
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { ViewBackendService } from '../../../stores/effects/folder/view/view_bd_svc';
|
import { ViewBackendService } from '../../../stores/effects/folder/view/view_bd_svc';
|
||||||
@ -9,6 +9,7 @@ export const usePageEvents = (page: IPage) => {
|
|||||||
const appDispatch = useAppDispatch();
|
const appDispatch = useAppDispatch();
|
||||||
const [showPageOptions, setShowPageOptions] = useState(false);
|
const [showPageOptions, setShowPageOptions] = useState(false);
|
||||||
const [showRenamePopup, setShowRenamePopup] = useState(false);
|
const [showRenamePopup, setShowRenamePopup] = useState(false);
|
||||||
|
const activePageId = useAppSelector((state) => state.activePageId);
|
||||||
const viewBackendService: ViewBackendService = new ViewBackendService(page.id);
|
const viewBackendService: ViewBackendService = new ViewBackendService(page.id);
|
||||||
const error = useError();
|
const error = useError();
|
||||||
|
|
||||||
@ -69,5 +70,6 @@ export const usePageEvents = (page: IPage) => {
|
|||||||
duplicatePage,
|
duplicatePage,
|
||||||
closePopup,
|
closePopup,
|
||||||
closeRenamePopup,
|
closeRenamePopup,
|
||||||
|
activePageId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -20,13 +20,16 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
|
|||||||
duplicatePage,
|
duplicatePage,
|
||||||
closePopup,
|
closePopup,
|
||||||
closeRenamePopup,
|
closeRenamePopup,
|
||||||
|
activePageId,
|
||||||
} = usePageEvents(page);
|
} = usePageEvents(page);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'relative'}>
|
<div className={'relative'}>
|
||||||
<div
|
<div
|
||||||
onClick={() => onPageClick()}
|
onClick={() => onPageClick()}
|
||||||
className={'flex cursor-pointer items-center justify-between rounded-lg py-2 pl-8 pr-4 hover:bg-surface-2 '}
|
className={`flex cursor-pointer items-center justify-between rounded-lg py-2 pl-8 pr-4 hover:bg-surface-2 ${
|
||||||
|
activePageId === page.id ? 'bg-surface-2' : ''
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<button className={'flex min-w-0 flex-1 items-center'}>
|
<button className={'flex min-w-0 flex-1 items-center'}>
|
||||||
<i className={'ml-1 mr-1 h-[16px] w-[16px]'}>
|
<i className={'ml-1 mr-1 h-[16px] w-[16px]'}>
|
||||||
|
@ -26,30 +26,25 @@ export const Screen = ({ children }: { children: ReactNode }) => {
|
|||||||
onScreenMouseMove,
|
onScreenMouseMove,
|
||||||
slideInFloatingPanel,
|
slideInFloatingPanel,
|
||||||
setFloatingPanelWidth,
|
setFloatingPanelWidth,
|
||||||
|
onHideMenuClick,
|
||||||
|
onShowMenuClick,
|
||||||
|
menuHidden,
|
||||||
} = useNavigationPanelHooks();
|
} = useNavigationPanelHooks();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onMouseMove={onScreenMouseMove} className='flex h-screen w-screen bg-white text-black'>
|
<div onMouseMove={onScreenMouseMove} className='flex h-screen w-screen bg-white text-black'>
|
||||||
{navigationPanelFixed ? (
|
<NavigationPanel
|
||||||
<NavigationPanel
|
onHideMenuClick={onHideMenuClick}
|
||||||
onCollapseNavigationClick={onCollapseNavigationClick}
|
width={width}
|
||||||
width={width}
|
folders={folders}
|
||||||
folders={folders}
|
pages={pages}
|
||||||
pages={pages}
|
onPageClick={onPageClick}
|
||||||
onPageClick={onPageClick}
|
menuHidden={menuHidden}
|
||||||
></NavigationPanel>
|
></NavigationPanel>
|
||||||
) : (
|
|
||||||
<NavigationFloatingPanel
|
|
||||||
onFixNavigationClick={onFixNavigationClick}
|
|
||||||
slideInFloatingPanel={slideInFloatingPanel}
|
|
||||||
folders={folders}
|
|
||||||
pages={pages}
|
|
||||||
onPageClick={onPageClick}
|
|
||||||
setWidth={setFloatingPanelWidth}
|
|
||||||
></NavigationFloatingPanel>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<MainPanel>{children}</MainPanel>
|
<MainPanel left={width} menuHidden={menuHidden} onShowMenuClick={onShowMenuClick}>
|
||||||
|
{children}
|
||||||
|
</MainPanel>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,7 @@ export async function assertTextCell(
|
|||||||
const cellController = await makeTextCellController(fieldId, rowInfo, databaseController).then((result) =>
|
const cellController = await makeTextCellController(fieldId, rowInfo, databaseController).then((result) =>
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
);
|
);
|
||||||
await cellController.subscribeChanged({
|
cellController.subscribeChanged({
|
||||||
onCellChanged: (value) => {
|
onCellChanged: (value) => {
|
||||||
const cellContent = value.unwrap();
|
const cellContent = value.unwrap();
|
||||||
if (cellContent !== expectedContent) {
|
if (cellContent !== expectedContent) {
|
||||||
|
@ -55,7 +55,7 @@ export class CellController<T, D> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeChanged = async (callbacks: Callbacks<T>) => {
|
subscribeChanged = (callbacks: Callbacks<T>) => {
|
||||||
this.subscribeCallbacks = callbacks;
|
this.subscribeCallbacks = callbacks;
|
||||||
this.cellDataNotifier.observer.subscribe((cellData) => {
|
this.cellDataNotifier.observer.subscribe((cellData) => {
|
||||||
if (cellData !== null) {
|
if (cellData !== null) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { OnNotificationError } from '../../../../../services/backend/notifications';
|
import { OnNotificationError, AFNotificationObserver } from '../../../../../services/backend/notifications';
|
||||||
import { AFNotificationObserver } from '../../../../../services/backend/notifications';
|
|
||||||
import { FolderNotificationParser } from './parser';
|
import { FolderNotificationParser } from './parser';
|
||||||
import { FlowyError, FolderNotification } from '../../../../../services/backend';
|
import { FlowyError, FolderNotification } from '../../../../../services/backend';
|
||||||
import { Result } from 'ts-results';
|
import { Result } from 'ts-results';
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
export const activePageIdSlice = createSlice({
|
||||||
|
name: 'activePageId',
|
||||||
|
initialState: '',
|
||||||
|
reducers: {
|
||||||
|
setActivePageId(state, action: PayloadAction<string>) {
|
||||||
|
return action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const activePageIdActions = activePageIdSlice.actions;
|
@ -1,6 +1,6 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
const initialState = 'field1';
|
const initialState = '';
|
||||||
|
|
||||||
export const boardSlice = createSlice({
|
export const boardSlice = createSlice({
|
||||||
name: 'board',
|
name: 'board',
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { FieldType } from '../../../../services/backend/models/flowy-database/field_entities';
|
import { FieldType } from '../../../../services/backend/models/flowy-database/field_entities';
|
||||||
import { DateFormat, NumberFormat, SelectOptionColorPB, TimeFormat } from '../../../../services/backend';
|
import { DateFormat, NumberFormat, SelectOptionColorPB, TimeFormat } from '../../../../services/backend';
|
||||||
|
|
||||||
@ -9,42 +8,35 @@ export interface ISelectOption {
|
|||||||
color?: SelectOptionColorPB;
|
color?: SelectOptionColorPB;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFieldOptions {
|
export interface ISelectOptionType {
|
||||||
selectOptions?: ISelectOption[];
|
selectOptions: ISelectOption[];
|
||||||
dateFormat?: DateFormat;
|
}
|
||||||
timeFormat?: TimeFormat;
|
|
||||||
includeTime?: boolean;
|
export interface IDateType {
|
||||||
numberFormat?: NumberFormat;
|
dateFormat: DateFormat;
|
||||||
|
timeFormat: TimeFormat;
|
||||||
|
includeTime: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INumberType {
|
||||||
|
numberFormat: NumberFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDatabaseField {
|
export interface IDatabaseField {
|
||||||
fieldId: string;
|
fieldId: string;
|
||||||
title: string;
|
title: string;
|
||||||
fieldType: FieldType;
|
fieldType: FieldType;
|
||||||
fieldOptions: IFieldOptions;
|
fieldOptions?: ISelectOptionType | IDateType | INumberType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDatabaseColumn {
|
export interface IDatabaseColumn {
|
||||||
fieldId: string;
|
fieldId: string;
|
||||||
sort: 'none' | 'asc' | 'desc';
|
sort: 'none' | 'asc' | 'desc';
|
||||||
filter?: any;
|
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICellData {
|
|
||||||
rowId: string;
|
|
||||||
fieldId: string;
|
|
||||||
cellId: string;
|
|
||||||
data: string | number;
|
|
||||||
optionIds?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DatabaseCellMap = { [keys: string]: ICellData };
|
|
||||||
|
|
||||||
export interface IDatabaseRow {
|
export interface IDatabaseRow {
|
||||||
rowId: string;
|
rowId: string;
|
||||||
// key(fieldId) -> value(Cell)
|
|
||||||
cells: DatabaseCellMap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DatabaseFieldMap = { [keys: string]: IDatabaseField };
|
export type DatabaseFieldMap = { [keys: string]: IDatabaseField };
|
||||||
@ -56,190 +48,47 @@ export interface IDatabase {
|
|||||||
columns: IDatabaseColumn[];
|
columns: IDatabaseColumn[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// key(databaseId) -> value(IDatabase)
|
|
||||||
const initialState: IDatabase = {
|
const initialState: IDatabase = {
|
||||||
title: 'Database One',
|
title: 'Database One',
|
||||||
columns: [
|
columns: [],
|
||||||
{
|
fields: {},
|
||||||
visible: true,
|
rows: [],
|
||||||
fieldId: 'field1',
|
|
||||||
sort: 'none',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
visible: true,
|
|
||||||
fieldId: 'field2',
|
|
||||||
sort: 'none',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
visible: true,
|
|
||||||
fieldId: 'field3',
|
|
||||||
sort: 'none',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
visible: true,
|
|
||||||
fieldId: 'field4',
|
|
||||||
sort: 'none',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fields: {
|
|
||||||
field1: {
|
|
||||||
title: 'status',
|
|
||||||
fieldId: 'field1',
|
|
||||||
fieldType: FieldType.SingleSelect,
|
|
||||||
fieldOptions: {
|
|
||||||
selectOptions: [
|
|
||||||
{
|
|
||||||
selectOptionId: 'so1',
|
|
||||||
title: 'To Do',
|
|
||||||
color: SelectOptionColorPB.Orange,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selectOptionId: 'so2',
|
|
||||||
title: 'In Progress',
|
|
||||||
color: SelectOptionColorPB.Green,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selectOptionId: 'so3',
|
|
||||||
title: 'Done',
|
|
||||||
color: SelectOptionColorPB.Blue,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
field2: {
|
|
||||||
title: 'name',
|
|
||||||
fieldId: 'field2',
|
|
||||||
fieldType: FieldType.RichText,
|
|
||||||
fieldOptions: {},
|
|
||||||
},
|
|
||||||
field3: {
|
|
||||||
title: 'percent',
|
|
||||||
fieldId: 'field3',
|
|
||||||
fieldType: FieldType.Number,
|
|
||||||
fieldOptions: {
|
|
||||||
numberFormat: NumberFormat.Num,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
field4: {
|
|
||||||
title: 'tags',
|
|
||||||
fieldId: 'field4',
|
|
||||||
fieldType: FieldType.MultiSelect,
|
|
||||||
fieldOptions: {
|
|
||||||
selectOptions: [
|
|
||||||
{
|
|
||||||
selectOptionId: 'f4so1',
|
|
||||||
title: 'type1',
|
|
||||||
color: SelectOptionColorPB.Blue,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selectOptionId: 'f4so2',
|
|
||||||
title: 'type2',
|
|
||||||
color: SelectOptionColorPB.Aqua,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selectOptionId: 'f4so3',
|
|
||||||
title: 'type3',
|
|
||||||
color: SelectOptionColorPB.Purple,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selectOptionId: 'f4so4',
|
|
||||||
title: 'type4',
|
|
||||||
color: SelectOptionColorPB.Purple,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selectOptionId: 'f4so5',
|
|
||||||
title: 'type5',
|
|
||||||
color: SelectOptionColorPB.Purple,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selectOptionId: 'f4so6',
|
|
||||||
title: 'type6',
|
|
||||||
color: SelectOptionColorPB.Purple,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selectOptionId: 'f4so7',
|
|
||||||
title: 'type7',
|
|
||||||
color: SelectOptionColorPB.Purple,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
rowId: 'row1',
|
|
||||||
cells: {
|
|
||||||
field1: {
|
|
||||||
rowId: 'row1',
|
|
||||||
fieldId: 'field1',
|
|
||||||
cellId: 'cell11',
|
|
||||||
data: '',
|
|
||||||
optionIds: ['so1'],
|
|
||||||
},
|
|
||||||
field2: {
|
|
||||||
rowId: 'row1',
|
|
||||||
fieldId: 'field2',
|
|
||||||
cellId: 'cell12',
|
|
||||||
data: 'Card 1',
|
|
||||||
},
|
|
||||||
field3: {
|
|
||||||
rowId: 'row1',
|
|
||||||
fieldId: 'field3',
|
|
||||||
cellId: 'cell13',
|
|
||||||
data: 10,
|
|
||||||
},
|
|
||||||
field4: {
|
|
||||||
rowId: 'row1',
|
|
||||||
fieldId: 'field4',
|
|
||||||
cellId: 'cell14',
|
|
||||||
data: '',
|
|
||||||
optionIds: ['f4so2', 'f4so3', 'f4so4', 'f4so5', 'f4so6', 'f4so7'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rowId: 'row2',
|
|
||||||
cells: {
|
|
||||||
field1: {
|
|
||||||
rowId: 'row2',
|
|
||||||
fieldId: 'field1',
|
|
||||||
cellId: 'cell21',
|
|
||||||
data: '',
|
|
||||||
optionIds: ['so1'],
|
|
||||||
},
|
|
||||||
field2: {
|
|
||||||
rowId: 'row2',
|
|
||||||
fieldId: 'field2',
|
|
||||||
cellId: 'cell22',
|
|
||||||
data: 'Card 2',
|
|
||||||
},
|
|
||||||
field3: {
|
|
||||||
rowId: 'row2',
|
|
||||||
fieldId: 'field3',
|
|
||||||
cellId: 'cell23',
|
|
||||||
data: 20,
|
|
||||||
},
|
|
||||||
field4: {
|
|
||||||
rowId: 'row2',
|
|
||||||
fieldId: 'field4',
|
|
||||||
cellId: 'cell24',
|
|
||||||
data: '',
|
|
||||||
optionIds: ['f4so1'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const databaseSlice = createSlice({
|
export const databaseSlice = createSlice({
|
||||||
name: 'database',
|
name: 'database',
|
||||||
initialState: initialState,
|
initialState: initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
|
clear: () => {
|
||||||
|
return initialState;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRows: (state, action: PayloadAction<{ rows: IDatabaseRow[] }>) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
rows: action.payload.rows,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFields: (state, action: PayloadAction<{ fields: DatabaseFieldMap }>) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
fields: action.payload.fields,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
updateColumns: (state, action: PayloadAction<{ columns: IDatabaseColumn[] }>) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
columns: action.payload.columns,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
updateTitle: (state, action: PayloadAction<{ title: string }>) => {
|
updateTitle: (state, action: PayloadAction<{ title: string }>) => {
|
||||||
state.title = action.payload.title;
|
state.title = action.payload.title;
|
||||||
},
|
},
|
||||||
|
|
||||||
addField: (state, action: PayloadAction<{ field: IDatabaseField }>) => {
|
/*addField: (state, action: PayloadAction<{ field: IDatabaseField }>) => {
|
||||||
const { field } = action.payload;
|
const { field } = action.payload;
|
||||||
|
|
||||||
state.fields[field.fieldId] = field;
|
state.fields[field.fieldId] = field;
|
||||||
@ -253,7 +102,7 @@ export const databaseSlice = createSlice({
|
|||||||
cells[field.fieldId] = {
|
cells[field.fieldId] = {
|
||||||
rowId: r.rowId,
|
rowId: r.rowId,
|
||||||
fieldId: field.fieldId,
|
fieldId: field.fieldId,
|
||||||
data: '',
|
data: [''],
|
||||||
cellId: nanoid(6),
|
cellId: nanoid(6),
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
@ -261,15 +110,15 @@ export const databaseSlice = createSlice({
|
|||||||
cells: cells,
|
cells: cells,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},*/
|
||||||
|
|
||||||
updateField: (state, action: PayloadAction<{ field: IDatabaseField }>) => {
|
/*updateField: (state, action: PayloadAction<{ field: IDatabaseField }>) => {
|
||||||
const { field } = action.payload;
|
const { field } = action.payload;
|
||||||
|
|
||||||
state.fields[field.fieldId] = field;
|
state.fields[field.fieldId] = field;
|
||||||
},
|
},*/
|
||||||
|
|
||||||
addFieldSelectOption: (state, action: PayloadAction<{ fieldId: string; option: ISelectOption }>) => {
|
/*addFieldSelectOption: (state, action: PayloadAction<{ fieldId: string; option: ISelectOption }>) => {
|
||||||
const { fieldId, option } = action.payload;
|
const { fieldId, option } = action.payload;
|
||||||
|
|
||||||
const field = state.fields[fieldId];
|
const field = state.fields[fieldId];
|
||||||
@ -283,9 +132,9 @@ export const databaseSlice = createSlice({
|
|||||||
selectOptions: [option],
|
selectOptions: [option],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},*/
|
||||||
|
|
||||||
updateFieldSelectOption: (state, action: PayloadAction<{ fieldId: string; option: ISelectOption }>) => {
|
/*updateFieldSelectOption: (state, action: PayloadAction<{ fieldId: string; option: ISelectOption }>) => {
|
||||||
const { fieldId, option } = action.payload;
|
const { fieldId, option } = action.payload;
|
||||||
|
|
||||||
const field = state.fields[fieldId];
|
const field = state.fields[fieldId];
|
||||||
@ -293,16 +142,16 @@ export const databaseSlice = createSlice({
|
|||||||
if (selectOptions) {
|
if (selectOptions) {
|
||||||
selectOptions[selectOptions.findIndex((o) => o.selectOptionId === option.selectOptionId)] = option;
|
selectOptions[selectOptions.findIndex((o) => o.selectOptionId === option.selectOptionId)] = option;
|
||||||
}
|
}
|
||||||
},
|
},*/
|
||||||
|
|
||||||
addRow: (state) => {
|
/*addRow: (state) => {
|
||||||
const rowId = nanoid(6);
|
const rowId = nanoid(6);
|
||||||
const cells: { [keys: string]: ICellData } = {};
|
const cells: { [keys: string]: ICellData } = {};
|
||||||
Object.keys(state.fields).forEach((id) => {
|
Object.keys(state.fields).forEach((id) => {
|
||||||
cells[id] = {
|
cells[id] = {
|
||||||
rowId: rowId,
|
rowId: rowId,
|
||||||
fieldId: id,
|
fieldId: id,
|
||||||
data: '',
|
data: [''],
|
||||||
cellId: nanoid(6),
|
cellId: nanoid(6),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -312,15 +161,15 @@ export const databaseSlice = createSlice({
|
|||||||
};
|
};
|
||||||
|
|
||||||
state.rows.push(newRow);
|
state.rows.push(newRow);
|
||||||
},
|
},*/
|
||||||
|
|
||||||
updateCellValue: (source, action: PayloadAction<{ cell: ICellData }>) => {
|
/*updateCellValue: (source, action: PayloadAction<{ cell: ICellData }>) => {
|
||||||
const { cell } = action.payload;
|
const { cell } = action.payload;
|
||||||
const row = source.rows.find((r) => r.rowId === cell.rowId);
|
const row = source.rows.find((r) => r.rowId === cell.rowId);
|
||||||
if (row) {
|
if (row) {
|
||||||
row.cells[cell.fieldId] = cell;
|
row.cells[cell.fieldId] = cell;
|
||||||
}
|
}
|
||||||
},
|
},*/
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import { workspaceSlice } from './reducers/workspace/slice';
|
|||||||
import { databaseSlice } from './reducers/database/slice';
|
import { databaseSlice } from './reducers/database/slice';
|
||||||
import { boardSlice } from './reducers/board/slice';
|
import { boardSlice } from './reducers/board/slice';
|
||||||
import { errorSlice } from './reducers/error/slice';
|
import { errorSlice } from './reducers/error/slice';
|
||||||
|
import { activePageIdSlice } from './reducers/activePageId/slice';
|
||||||
|
|
||||||
const listenerMiddlewareInstance = createListenerMiddleware({
|
const listenerMiddlewareInstance = createListenerMiddleware({
|
||||||
onError: () => console.error,
|
onError: () => console.error,
|
||||||
@ -25,6 +26,7 @@ const store = configureStore({
|
|||||||
reducer: {
|
reducer: {
|
||||||
[foldersSlice.name]: foldersSlice.reducer,
|
[foldersSlice.name]: foldersSlice.reducer,
|
||||||
[pagesSlice.name]: pagesSlice.reducer,
|
[pagesSlice.name]: pagesSlice.reducer,
|
||||||
|
[activePageIdSlice.name]: activePageIdSlice.reducer,
|
||||||
[navigationWidthSlice.name]: navigationWidthSlice.reducer,
|
[navigationWidthSlice.name]: navigationWidthSlice.reducer,
|
||||||
[currentUserSlice.name]: currentUserSlice.reducer,
|
[currentUserSlice.name]: currentUserSlice.reducer,
|
||||||
[gridSlice.name]: gridSlice.reducer,
|
[gridSlice.name]: gridSlice.reducer,
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
export class Log {
|
export class Log {
|
||||||
static error(msg?: any) {
|
static error(...msg: unknown[]) {
|
||||||
console.log(msg);
|
console.log(...msg);
|
||||||
}
|
}
|
||||||
static info(msg?: any) {
|
static info(...msg: unknown[]) {
|
||||||
console.log(msg);
|
console.log(...msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static debug(msg?: any) {
|
static debug(...msg: unknown[]) {
|
||||||
console.log(msg);
|
console.log(...msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static trace(msg?: any) {
|
static trace(...msg: unknown[]) {
|
||||||
console.log(msg);
|
console.log(...msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static warn(msg?: any) {
|
static warn(...msg: unknown[]) {
|
||||||
console.log(msg);
|
console.log(...msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,19 @@ import { Board } from '../components/board/Board';
|
|||||||
|
|
||||||
export const BoardPage = () => {
|
export const BoardPage = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const [databaseId, setDatabaseId] = useState('');
|
const [viewId, setViewId] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (params?.id?.length) {
|
if (params?.id?.length) {
|
||||||
// setDatabaseId(params.id);
|
setViewId(params.id);
|
||||||
setDatabaseId('testDb');
|
// setDatabaseId('testDb');
|
||||||
}
|
}
|
||||||
}, [params]);
|
}, [params]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex h-full flex-col gap-8 px-8 pt-8'>
|
<div className='flex h-full flex-col gap-8 px-8 pt-8'>
|
||||||
<h1 className='text-4xl font-bold'>Board</h1>
|
<h1 className='text-4xl font-bold'>Board: {viewId}</h1>
|
||||||
{databaseId?.length && <Board />}
|
{viewId?.length && <Board viewId={viewId} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -102,7 +102,6 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
|
|||||||
filters.push(format!("dart_ffi={}", "info"));
|
filters.push(format!("dart_ffi={}", "info"));
|
||||||
filters.push(format!("flowy_sqlite={}", "info"));
|
filters.push(format!("flowy_sqlite={}", "info"));
|
||||||
filters.push(format!("flowy_net={}", "info"));
|
filters.push(format!("flowy_net={}", "info"));
|
||||||
|
|
||||||
#[cfg(feature = "profiling")]
|
#[cfg(feature = "profiling")]
|
||||||
filters.push(format!("tokio={}", level));
|
filters.push(format!("tokio={}", level));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user