diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/EditCheckListPopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/EditCheckListPopup.tsx index 45f4d4efb6..b629efc854 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/EditCheckListPopup.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/EditCheckListPopup.tsx @@ -53,6 +53,7 @@ export const EditCheckListPopup = ({ const onDeleteOptionClick = async () => { const svc = new SelectOptionCellBackendService(cellIdentifier); await svc.deleteOption([editingSelectOption]); + onOutsideClick(); }; return ( diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx index c748de3c17..a17afe1277 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx @@ -1,4 +1,4 @@ -import { MouseEventHandler, useEffect, useRef, useState } from 'react'; +import { FocusEventHandler, MouseEventHandler, useEffect, useRef, useState } from 'react'; import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon'; import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName'; import { useTranslation } from 'react-i18next'; @@ -36,12 +36,24 @@ export const EditFieldPopup = ({ const databaseStore = useAppSelector((state) => state.database); const { t } = useTranslation(); const changeTypeButtonRef = useRef(null); + const inputRef = useRef(null); const [name, setName] = useState(''); useEffect(() => { setName(databaseStore.fields[cellIdentifier.fieldId].title); }, [databaseStore, cellIdentifier]); + // focus input on mount + useEffect(() => { + if (!inputRef.current || !name) return; + inputRef.current.focus(); + }, [inputRef, name]); + + const selectAll: FocusEventHandler = (e) => { + e.target.selectionStart = 0; + e.target.selectionEnd = e.target.value.length; + }; + const save = async () => { if (!fieldInfo) return; const controller = new TypeOptionController(viewId, Some(fieldInfo)); @@ -80,6 +92,8 @@ export const EditFieldPopup = ({ >
setName(e.target.value)} onBlur={() => save()} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx index f592a21dd4..673aab5687 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx @@ -22,6 +22,7 @@ import { EditCheckListPopup } from '$app/components/_shared/EditRow/CheckList/Ed import { PropertiesPanel } from '$app/components/_shared/EditRow/PropertiesPanel'; import { ImageSvg } from '$app/components/_shared/svg/ImageSvg'; import { PromptWindow } from '$app/components/_shared/PromptWindow'; +import { useAppSelector } from '$app/stores/store'; export const EditRow = ({ onClose, @@ -34,6 +35,7 @@ export const EditRow = ({ controller: DatabaseController; rowInfo: RowInfo; }) => { + const databaseStore = useAppSelector((state) => state.database); const { cells, onNewColumnClick } = useRow(viewId, controller, rowInfo); const { t } = useTranslation(); const [unveil, setUnveil] = useState(false); @@ -226,19 +228,21 @@ export const EditRow = ({ showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto' }`} > - {cells.map((cell, cellIndex) => ( - - ))} + {cells + .filter((cell) => databaseStore.fields[cell.cellIdentifier.fieldId].visible) + .map((cell, cellIndex) => ( + + ))}
)} @@ -261,6 +265,7 @@ export const EditRow = ({ controller={controller} rowInfo={rowInfo} onDeletePropertyClick={onDeletePropertyClick} + onNewColumnClick={onNewColumnClick} > @@ -292,7 +297,7 @@ export const EditRow = ({ cellIdentifier={editingCell} cellCache={controller.databaseViewCache.getRowCache().getCellCache()} fieldController={controller.fieldController} - onOutsideClick={() => setShowChangeOptionsPopup(false)} + onOutsideClick={() => !showEditCellOption && setShowChangeOptionsPopup(false)} openOptionDetail={onOpenOptionDetailClick} > )} @@ -335,7 +340,7 @@ export const EditRow = ({ cellIdentifier={editingCell} cellCache={controller.databaseViewCache.getRowCache().getCellCache()} fieldController={controller.fieldController} - onOutsideClick={() => setShowCheckListPopup(false)} + onOutsideClick={() => !showEditCheckList && setShowCheckListPopup(false)} openCheckListDetail={onOpenCheckListDetailClick} > )} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/EditCellOptionPopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/EditCellOptionPopup.tsx index 3f33f48c23..08609e1679 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/EditCellOptionPopup.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/EditCellOptionPopup.tsx @@ -67,6 +67,7 @@ export const EditCellOptionPopup = ({ const onDeleteOptionClick = async () => { const svc = new SelectOptionCellBackendService(cellIdentifier); await svc.deleteOption([editingSelectOption]); + onOutsideClick(); }; return ( diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/PropertiesPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/PropertiesPanel.tsx index f037048c79..3f17683de7 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/PropertiesPanel.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/PropertiesPanel.tsx @@ -1,5 +1,5 @@ import { DropDownShowSvg } from '$app/components/_shared/svg/DropDownShowSvg'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useRow } from '$app/components/_shared/database-hooks/useRow'; import { DatabaseController } from '$app/stores/effects/database/database_controller'; import { RowInfo } from '$app/stores/effects/database/row/row_cache'; @@ -12,6 +12,9 @@ import { TrashSvg } from '$app/components/_shared/svg/TrashSvg'; import { MultiSelectTypeSvg } from '$app/components/_shared/svg/MultiSelectTypeSvg'; import { DocumentSvg } from '$app/components/_shared/svg/DocumentSvg'; import { SingleSelectTypeSvg } from '$app/components/_shared/svg/SingleSelectTypeSvg'; +import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller'; +import { Some } from 'ts-results'; +import { useTranslation } from 'react-i18next'; const typesOrder: FieldType[] = [ FieldType.RichText, @@ -29,28 +32,67 @@ export const PropertiesPanel = ({ controller, rowInfo, onDeletePropertyClick, + onNewColumnClick, }: { viewId: string; controller: DatabaseController; rowInfo: RowInfo; onDeletePropertyClick: (fieldId: string) => void; + onNewColumnClick: (initialFieldType: FieldType, name?: string) => Promise; }) => { const { cells } = useRow(viewId, controller, rowInfo); const databaseStore = useAppSelector((state) => state.database); + const { t } = useTranslation(); const [showAddedProperties, setShowAddedProperties] = useState(true); const [showBasicProperties, setShowBasicProperties] = useState(false); const [showAdvancedProperties, setShowAdvancedProperties] = useState(false); const [hoveredPropertyIndex, setHoveredPropertyIndex] = useState(-1); - const [hiddenProperties, setHiddenProperties] = useState([]); - useEffect(() => { - setHiddenProperties(cells.map(() => false)); - }, [cells]); + const toggleHideProperty = async (v: boolean, index: number) => { + const fieldInfo = controller.fieldController.getField(cells[index].fieldId); + if (fieldInfo) { + const typeController = new TypeOptionController(viewId, Some(fieldInfo)); + await typeController.initialize(); + if (fieldInfo.field.visibility) { + await typeController.hideField(); + } else { + await typeController.showField(); + } + } + }; - const toggleHideProperty = (v: boolean, index: number) => { - setHiddenProperties(hiddenProperties.map((h, i) => (i === index ? !v : h))); + const addSelectedFieldType = async (fieldType: FieldType) => { + let name = 'New Field'; + switch (fieldType) { + case FieldType.RichText: + name = t('grid.field.textFieldName'); + break; + case FieldType.Number: + name = t('grid.field.numberFieldName'); + break; + case FieldType.DateTime: + name = t('grid.field.dateFieldName'); + break; + case FieldType.SingleSelect: + name = t('grid.field.singleSelectFieldName'); + break; + case FieldType.MultiSelect: + name = t('grid.field.multiSelectFieldName'); + break; + case FieldType.Checklist: + name = t('grid.field.checklistFieldName'); + break; + case FieldType.URL: + name = t('grid.field.urlFieldName'); + break; + case FieldType.Checkbox: + name = t('grid.field.checkboxFieldName'); + break; + } + + await onNewColumnClick(fieldType, name); }; return ( @@ -91,7 +133,10 @@ export const PropertiesPanel = ({ > - toggleHideProperty(v, cellIndex)}> + toggleHideProperty(v, cellIndex)} + > ))} @@ -108,17 +153,17 @@ export const PropertiesPanel = ({
{showBasicProperties && (
- {typesOrder.map((t, i) => ( + {typesOrder.map((type, i) => ( ))} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/loadField.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/loadField.ts index ad0b7626da..2836bb5564 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/loadField.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/loadField.ts @@ -1,6 +1,6 @@ -import { TypeOptionController } from '../../../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 { IDatabaseField, ISelectOption } from '../../../stores/reducers/database/slice'; +import { IDatabaseField, ISelectOption } from '$app_reducers/database/slice'; import { ChecklistTypeOptionPB, FieldType, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB } from '@/services/backend'; import { makeChecklistTypeOptionContext, @@ -8,10 +8,10 @@ import { 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'; +} from '$app/stores/effects/database/field/type_option/type_option_context'; +import { boardActions } from '$app_reducers/board/slice'; +import { FieldInfo } from '$app/stores/effects/database/field/field_controller'; +import { AppDispatch } from '$app/stores/store'; export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?: AppDispatch): Promise { const field = fieldInfo.field; @@ -53,6 +53,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?: fieldId: field.id, title: field.name, fieldType: field.field_type, + visible: field.visibility, fieldOptions: { selectOptions, }, @@ -64,6 +65,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?: return { fieldId: field.id, title: field.name, + visible: field.visibility, fieldType: field.field_type, fieldOptions: { numberFormat: typeOption.format, @@ -76,6 +78,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?: return { fieldId: field.id, title: field.name, + visible: field.visibility, fieldType: field.field_type, fieldOptions: { dateFormat: typeOption.date_format, @@ -88,6 +91,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?: return { fieldId: field.id, title: field.name, + visible: field.visibility, fieldType: field.field_type, }; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useCell.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useCell.ts index a61607e9bd..8acd82aa11 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useCell.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useCell.ts @@ -2,13 +2,17 @@ import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc'; import { CellCache } from '$app/stores/effects/database/cell/cell_cache'; import { FieldController } from '$app/stores/effects/database/field/field_controller'; import { CellControllerBuilder } from '$app/stores/effects/database/cell/controller_builder'; -import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '$app/../services/backend'; +import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '@/services/backend'; import { useEffect, useState } from 'react'; import { CellController } from '$app/stores/effects/database/cell/cell_controller'; +import { useAppDispatch, useAppSelector } from '$app/stores/store'; +import { databaseActions, ISelectOptionType } from '$app_reducers/database/slice'; export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => { const [data, setData] = useState(); const [cellController, setCellController] = useState>(); + const databaseStore = useAppSelector((state) => state.database); + const dispatch = useAppDispatch(); useEffect(() => { if (!cellIdentifier || !cellCache || !fieldController) return; @@ -19,7 +23,34 @@ export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fi c.subscribeChanged({ onCellChanged: (cellData) => { if (cellData.some) { - setData(cellData.val); + const value = cellData.val; + setData(value); + + // update redux store for database field if there are new select options + if ( + value instanceof SelectOptionCellDataPB && + (databaseStore.fields[cellIdentifier.fieldId].fieldOptions as ISelectOptionType).selectOptions.length !== + value.options.length + ) { + const field = { ...databaseStore.fields[cellIdentifier.fieldId] }; + const selectOptions = value.options.map((option) => ({ + selectOptionId: option.id, + title: option.name, + color: option.color, + })); + + dispatch( + databaseActions.updateField({ + field: { + ...field, + fieldOptions: { + ...field.fieldOptions, + selectOptions: selectOptions, + }, + }, + }) + ); + } } }, }); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts index 22cbb3111a..2c13ecf4c1 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts @@ -45,12 +45,12 @@ export const useDatabase = (viewId: string, type?: ViewLayoutPB) => { }; useEffect(() => { - if (!controller) return; - void (async () => { + if (!controller) return; controller.subscribe({ onRowsChanged: (rowInfos) => { - setRows(rowInfos); + // TODO: this is a hack to make sure that the row cache is updated + setRows([...rowInfos]); }, onFieldsChanged: (fieldInfos) => { void loadFields(fieldInfos); @@ -72,6 +72,10 @@ export const useDatabase = (viewId: string, type?: ViewLayoutPB) => { setGroups(controller.groups.value); } })(); + + return () => { + void controller?.dispose(); + }; }, [controller]); const onNewRowClick = async (index: number) => { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useRow.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useRow.ts index 30c3c1f7be..eb1182b844 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useRow.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useRow.ts @@ -4,8 +4,9 @@ import { RowInfo } from '$app/stores/effects/database/row/row_cache'; import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc'; import { useEffect, useState } from 'react'; import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller'; -import { None } from 'ts-results'; import { useAppSelector } from '$app/stores/store'; +import { FieldType } from '@/services/backend'; +import { None } from 'ts-results'; export const useRow = (viewId: string, databaseController: DatabaseController, rowInfo: RowInfo) => { const [cells, setCells] = useState<{ fieldId: string; cellIdentifier: CellIdentifier }[]>([]); @@ -42,10 +43,13 @@ export const useRow = (viewId: string, databaseController: DatabaseController, r })(); }, [rowController, databaseStore.columns]); - const onNewColumnClick = async () => { + const onNewColumnClick = async (initialFieldType: FieldType = FieldType.RichText, name?: string) => { if (!databaseController) return; - const controller = new TypeOptionController(viewId, None); + const controller = new TypeOptionController(viewId, None, initialFieldType); await controller.initialize(); + if (name) { + await controller.setFieldName(name); + } }; return { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx index 5af7e0f8e2..a79a2c0a36 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx @@ -9,7 +9,7 @@ import { EditRow } from '$app/components/_shared/EditRow/EditRow'; import { BoardToolbar } from '$app/components/board/BoardToolbar'; export const Board = ({ viewId, title }: { viewId: string; title: string }) => { - const { controller, rows, groups, groupByFieldId, onNewRowClick, onDragEnd } = useDatabase(viewId, ViewLayoutPB.Board); + const { controller, groups, groupByFieldId, onNewRowClick, onDragEnd } = useDatabase(viewId, ViewLayoutPB.Board); const [showBoardRow, setShowBoardRow] = useState(false); const [boardRowInfo, setBoardRowInfo] = useState(); @@ -38,7 +38,6 @@ export const Board = ({ viewId, title }: { viewId: string; title: string }) => { viewId={viewId} controller={controller} group={group} - allRows={rows} groupByFieldId={groupByFieldId} onNewRowClick={() => onNewRowClick(index)} onOpenRow={onOpenRow} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx index ef950e17ad..7403b33fa9 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx @@ -9,6 +9,7 @@ import { PopupWindow } from '$app/components/_shared/PopupWindow'; import { TrashSvg } from '$app/components/_shared/svg/TrashSvg'; import { RowBackendService } from '$app/stores/effects/database/row/row_bd_svc'; import { useTranslation } from 'react-i18next'; +import { useAppSelector } from '$app/stores/store'; export const BoardCard = ({ index, @@ -25,6 +26,7 @@ export const BoardCard = ({ groupByFieldId: string; onOpenRow: (rowId: RowInfo) => void; }) => { + const databaseStore = useAppSelector((state) => state.database); const { t } = useTranslation(); const { cells } = useRow(viewId, controller, rowInfo); @@ -70,7 +72,9 @@ export const BoardCard = ({
{cells - .filter((cell) => cell.fieldId !== groupByFieldId) + .filter( + (cell) => cell.fieldId !== groupByFieldId && databaseStore.fields[cell.cellIdentifier.fieldId].visible + ) .map((cell, cellIndex) => ( void; onOpenRow: (rowId: RowInfo) => void; @@ -26,6 +25,23 @@ export const BoardGroup = ({ }) => { const { t } = useTranslation(); + const [rows, setRows] = useState([]); + useEffect(() => { + const reloadRows = () => { + setRows(group.rows.map((rowPB) => new RowInfo(viewId, controller.fieldController.fieldInfos, rowPB))); + }; + reloadRows(); + group.subscribe({ + onRemoveRow: reloadRows, + onInsertRow: reloadRows, + onUpdateRow: reloadRows, + onCreateRow: reloadRows, + }); + return () => { + group.unsubscribe(); + }; + }, [controller, group, viewId]); + return (
@@ -49,9 +65,8 @@ export const BoardGroup = ({ {...provided.droppableProps} ref={provided.innerRef} > - {group.rows.map((row_pb, index) => { - const row = allRows.find((r) => r.row.id === row_pb.id); - return row ? ( + {rows.map((row, index) => { + return ( - ) : ( - ); })}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts index 15738fc6c0..9a89c9f79a 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts @@ -32,21 +32,20 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => { // Backend services const appBackendService = new AppBackendService(folder.id); - const workspaceBackendService = new WorkspaceBackendService(workspace.id ?? ''); useEffect(() => { void appObserver.subscribe({ - onAppChanged: (change) => { - if (change.ok) { - const views = change.val; - const updatedPages: IPage[] = views.items.map((view) => ({ - id: view.id, - folderId: view.parent_view_id, - pageType: view.layout, - title: view.name, - })); - appDispatch(pagesActions.didReceivePages(updatedPages)); - } + onViewsChanged: async () => { + const result = await appBackendService.getAllViews(); + if (!result.ok) return; + const views = result.val; + const updatedPages: IPage[] = views.map((view) => ({ + id: view.id, + folderId: view.parent_view_id, + pageType: view.layout, + title: view.name, + })); + appDispatch(pagesActions.didReceivePages({ pages: updatedPages, folderId: folder.id })); }, }); return () => { @@ -100,6 +99,7 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => { const duplicateFolder = async () => { closePopup(); + const workspaceBackendService = new WorkspaceBackendService(workspace.id ?? ''); const newApp = await workspaceBackendService.createApp({ name: folder.title, }); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts index a3ef4c1752..60543ddea7 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.hooks.ts @@ -1,6 +1,6 @@ -import { useAppSelector } from '../../../stores/store'; +import { useAppSelector } from '$app/stores/store'; import { useNavigate } from 'react-router-dom'; -import { IPage } from '../../../stores/reducers/pages/slice'; +import { IPage } from '$app_reducers/pages/slice'; import { ViewLayoutPB } from '@/services/backend'; import { useState } from 'react'; @@ -21,11 +21,10 @@ export const useNavigationPanelHooks = function () { }; const onPageClick = (page: IPage) => { - let pageTypeRoute = (() => { + const pageTypeRoute = (() => { switch (page.pageType) { case ViewLayoutPB.Document: return 'document'; - break; case ViewLayoutPB.Grid: return 'grid'; case ViewLayoutPB.Board: diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx index 309c26f8db..2808cef8b7 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx @@ -4,11 +4,11 @@ import { FolderItem } from './FolderItem'; import { TrashButton } from './TrashButton'; import { NewFolderButton } from './NewFolderButton'; import { NavigationResizer } from './NavigationResizer'; -import { IFolder } from '../../../stores/reducers/folders/slice'; -import { IPage } from '../../../stores/reducers/pages/slice'; +import { IFolder } from '$app_reducers/folders/slice'; +import { IPage } from '$app_reducers/pages/slice'; import { useLocation, useNavigate } from 'react-router-dom'; import React, { useEffect, useRef, useState } from 'react'; -import { useAppSelector } from '../../../stores/store'; +import { useAppSelector } from '$app/stores/store'; import { ANIMATION_DURATION, FOLDER_MARGIN, @@ -59,7 +59,7 @@ export const NavigationPanel = ({ let height = 0; for (let i = 0; i < folderIndex; i++) { height += INITIAL_FOLDER_HEIGHT + FOLDER_MARGIN; - if (foldersStore[i].showPages === true) { + if (foldersStore[i].showPages) { height += pagesStore.filter((p) => p.folderId === foldersStore[i].id).length * PAGE_ITEM_HEIGHT; } } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationResizer.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationResizer.tsx index cba0c35a8d..a3cbe56032 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationResizer.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationResizer.tsx @@ -1,7 +1,7 @@ import { useResizer } from '../../_shared/useResizer'; -import { useAppDispatch, useAppSelector } from '../../../stores/store'; +import { useAppDispatch, useAppSelector } from '$app/stores/store'; import { useEffect } from 'react'; -import { navigationWidthActions } from '../../../stores/reducers/navigation-width/slice'; +import { navigationWidthActions } from '$app_reducers/navigation-width/slice'; export const NavigationResizer = ({ minWidth }: { minWidth: number }) => { const width = useAppSelector((state) => state.navigationWidth); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.hooks.ts index fa48f1cde9..2c9fc11a79 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.hooks.ts @@ -1,6 +1,6 @@ -import { useAppDispatch, useAppSelector } from '../../../stores/store'; -import { foldersActions } from '../../../stores/reducers/folders/slice'; -import { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc'; +import { useAppDispatch, useAppSelector } from '$app/stores/store'; +import { foldersActions } from '$app_reducers/folders/slice'; +import { WorkspaceBackendService } from '$app/stores/effects/folder/workspace/workspace_bd_svc'; export const useNewFolder = () => { const appDispatch = useAppDispatch(); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts index ccb865c6f3..1b21ce46db 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts @@ -1,7 +1,6 @@ import { IPage, pagesActions } from '$app_reducers/pages/slice'; import { useAppDispatch } from '$app/stores/store'; import { useEffect, useState } from 'react'; -import { nanoid } from 'nanoid'; import { ViewBackendService } from '$app/stores/effects/folder/view/view_bd_svc'; import { useLocation } from 'react-router-dom'; import { ViewPB } from '@/services/backend'; @@ -43,10 +42,7 @@ export const usePageEvents = (page: IPage) => { const duplicatePage = async () => { closePopup(); - await viewBackendService.duplicate(ViewPB.fromObject({})); - appDispatch( - pagesActions.addPage({ id: nanoid(8), pageType: page.pageType, title: page.title, folderId: page.folderId }) - ); + await viewBackendService.duplicate(ViewPB.fromObject(page)); }; const closePopup = () => { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.tsx index e00f37d0f9..e9f5e79d00 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.tsx @@ -3,7 +3,7 @@ import { BoardSvg } from '../../_shared/svg/BoardSvg'; import { GridSvg } from '../../_shared/svg/GridSvg'; import { Details2Svg } from '../../_shared/svg/Details2Svg'; import { NavItemOptionsPopup } from './NavItemOptionsPopup'; -import { IPage } from '../../../stores/reducers/pages/slice'; +import { IPage } from '$app_reducers/pages/slice'; import { Button } from '../../_shared/Button'; import { usePageEvents } from './PageItem.hooks'; import { RenamePopup } from './RenamePopup'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/Screen.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/Screen.tsx index 19f8e97542..a8aad7cac7 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/Screen.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/Screen.tsx @@ -3,7 +3,7 @@ import { NavigationPanel } from './NavigationPanel/NavigationPanel'; import { MainPanel } from './MainPanel'; import { useNavigationPanelHooks } from './NavigationPanel/NavigationPanel.hooks'; import { useWorkspace } from './Workspace.hooks'; -import { useAppSelector } from '../../stores/store'; +import { useAppSelector } from '$app/stores/store'; export const Screen = ({ children }: { children: ReactNode }) => { const currentUser = useAppSelector((state) => state.currentUser); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/Workspace.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/Workspace.hooks.ts index bfa8a514f8..fd76e8594e 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/Workspace.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/Workspace.hooks.ts @@ -1,8 +1,10 @@ -import { foldersActions } from '../../stores/reducers/folders/slice'; -import { useAppDispatch, useAppSelector } from '../../stores/store'; -import { pagesActions } from '../../stores/reducers/pages/slice'; -import { workspaceActions } from '../../stores/reducers/workspace/slice'; -import { UserBackendService } from '../../stores/effects/user/user_bd_svc'; +import { foldersActions } from '$app_reducers/folders/slice'; +import { useAppDispatch, useAppSelector } from '$app/stores/store'; +import { pagesActions } from '$app_reducers/pages/slice'; +import { workspaceActions } from '$app_reducers/workspace/slice'; +import { UserBackendService } from '$app/stores/effects/user/user_bd_svc'; +import { AppBackendService } from '$app/stores/effects/folder/app/app_bd_svc'; +import { Log } from '$app/utils/log'; export const useWorkspace = () => { const currentUser = useAppSelector((state) => state.currentUser); @@ -22,10 +24,16 @@ export const useWorkspace = () => { const apps = workspace.views; for (const app of apps) { appDispatch(foldersActions.addFolder({ id: app.id, title: app.name })); - - const views = app.child_views; - for (const view of views) { - appDispatch(pagesActions.addPage({ folderId: app.id, id: view.id, pageType: view.layout, title: view.name })); + const service = new AppBackendService(app.id); + const result = await service.getAllViews(); + if (result.ok) { + for (const view of result.val) { + appDispatch( + pagesActions.addPage({ folderId: app.id, id: view.id, pageType: view.layout, title: view.name }) + ); + } + } else { + Log.error('Failed to get views, folderId: ' + app.id); } } } catch (e1) { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/WorkspaceUser.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/WorkspaceUser.tsx index c85e1c757b..84e8de5fb4 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/WorkspaceUser.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/WorkspaceUser.tsx @@ -1,4 +1,4 @@ -import { useAppSelector } from '../../stores/store'; +import { useAppSelector } from '$app/stores/store'; export const WorkspaceUser = () => { const currentUser = useAppSelector((state) => state.currentUser); diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_controller.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_controller.ts index 86fd441cab..b44b6fa12f 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_controller.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_controller.ts @@ -73,6 +73,22 @@ export class TypeOptionController { } }; + hideField = async () => { + if (this.fieldBackendSvc) { + void this.fieldBackendSvc.updateField({ visibility: false }); + } else { + throw Error('Unexpected empty field backend service'); + } + }; + + showField = async () => { + if (this.fieldBackendSvc) { + void this.fieldBackendSvc.updateField({ visibility: true }); + } else { + throw Error('Unexpected empty field backend service'); + } + }; + saveTypeOption = async (data: Uint8Array) => { if (this.typeOptionData.some) { this.typeOptionData.val.type_option_data = data; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/app/app_observer.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/app/app_observer.ts index 47ff37333f..5c59f4eb61 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/app/app_observer.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/app/app_observer.ts @@ -1,40 +1,34 @@ -import { Ok, Result } from "ts-results"; -import { FlowyError, FolderNotification, RepeatedViewPB } from "@/services/backend"; -import { ChangeNotifier } from "$app/utils/change_notifier"; -import { FolderNotificationObserver } from "../notifications/observer"; - -export type AppUpdateNotifyCallback = (value: Result) => void; +import { FolderNotification } from '@/services/backend'; +import { ChangeNotifier } from '$app/utils/change_notifier'; +import { FolderNotificationObserver } from '../notifications/observer'; export class AppObserver { - _appNotifier = new ChangeNotifier>(); + _viewsNotifier = new ChangeNotifier(); _listener?: FolderNotificationObserver; - constructor(public readonly appId: string) { - } + constructor(public readonly appId: string) {} - subscribe = async (callbacks: { onAppChanged: AppUpdateNotifyCallback }) => { - this._appNotifier?.observer?.subscribe(callbacks.onAppChanged); + subscribe = async (callbacks: { onViewsChanged: () => void }) => { + this._viewsNotifier?.observer?.subscribe(callbacks.onViewsChanged); this._listener = new FolderNotificationObserver({ viewId: this.appId, parserHandler: (notification, result) => { switch (notification) { - case FolderNotification.DidUpdateWorkspaceViews: + case FolderNotification.DidUpdateChildViews: if (result.ok) { - this._appNotifier?.notify(Ok(RepeatedViewPB.deserializeBinary(result.val))); - } else { - this._appNotifier?.notify(result); + this._viewsNotifier?.notify(); } break; default: break; } - } + }, }); await this._listener.start(); }; unsubscribe = async () => { - this._appNotifier.unsubscribe(); + this._viewsNotifier.unsubscribe(); await this._listener?.stop(); }; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/database/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/database/slice.ts index ebb1c8ec3f..f3592eabce 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/database/slice.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/database/slice.ts @@ -25,6 +25,7 @@ export interface INumberType { export interface IDatabaseField { fieldId: string; title: string; + visible: boolean; fieldType: FieldType; fieldOptions?: ISelectOptionType | IDateType | INumberType; } @@ -112,11 +113,10 @@ export const databaseSlice = createSlice({ }); },*/ - /*updateField: (state, action: PayloadAction<{ field: IDatabaseField }>) => { + updateField: (state, action: PayloadAction<{ field: IDatabaseField }>) => { const { field } = action.payload; - state.fields[field.fieldId] = field; - },*/ + }, /*addFieldSelectOption: (state, action: PayloadAction<{ fieldId: string; option: ISelectOption }>) => { const { fieldId, option } = action.payload; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts index 7ad447601c..7ef7ba06ec 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts @@ -14,8 +14,8 @@ export const pagesSlice = createSlice({ name: 'pages', initialState: initialState, reducers: { - didReceivePages(state, action: PayloadAction) { - return action.payload; + didReceivePages(state, action: PayloadAction<{ pages: IPage[]; folderId: string }>) { + return state.filter((page) => page.folderId !== action.payload.folderId).concat(action.payload.pages); }, addPage(state, action: PayloadAction) { state.push(action.payload);