Fix/tauri board events (#2678)

* chore: focus name input on field edit

* fix: remove random id for duplicated view

* fix: use alias for imports

* fix: folder, grid, board bugs

* chore: remove log

* fix: update options list on add

* chore: close on delete option

* chore: show and hide field

* chore: add field with specific type

* chore: small cleanup

* fix: create view on another folder and views notifier reorganize

---------

Co-authored-by: qinluhe <qinluhe.twodog@gmail.com>
This commit is contained in:
Askarbek Zadauly 2023-06-05 14:48:34 +06:00 committed by GitHub
parent 4f2585baed
commit 6a43dd871d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 254 additions and 116 deletions

View File

@ -53,6 +53,7 @@ export const EditCheckListPopup = ({
const onDeleteOptionClick = async () => {
const svc = new SelectOptionCellBackendService(cellIdentifier);
await svc.deleteOption([editingSelectOption]);
onOutsideClick();
};
return (

View File

@ -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<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(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<HTMLInputElement> = (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 = ({
>
<div className={'flex flex-col gap-2'}>
<input
ref={inputRef}
onFocus={selectAll}
value={name}
onChange={(e) => setName(e.target.value)}
onBlur={() => save()}

View File

@ -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) => (
<EditCellWrapper
index={cellIndex}
key={cellIndex}
cellIdentifier={cell.cellIdentifier}
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
fieldController={controller.fieldController}
onEditFieldClick={onEditFieldClick}
onEditOptionsClick={onEditOptionsClick}
onEditDateClick={onEditDateClick}
onEditCheckListClick={onEditCheckListClick}
></EditCellWrapper>
))}
{cells
.filter((cell) => databaseStore.fields[cell.cellIdentifier.fieldId].visible)
.map((cell, cellIndex) => (
<EditCellWrapper
index={cellIndex}
key={cellIndex}
cellIdentifier={cell.cellIdentifier}
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
fieldController={controller.fieldController}
onEditFieldClick={onEditFieldClick}
onEditOptionsClick={onEditOptionsClick}
onEditDateClick={onEditDateClick}
onEditCheckListClick={onEditCheckListClick}
></EditCellWrapper>
))}
</div>
)}
</Droppable>
@ -261,6 +265,7 @@ export const EditRow = ({
controller={controller}
rowInfo={rowInfo}
onDeletePropertyClick={onDeletePropertyClick}
onNewColumnClick={onNewColumnClick}
></PropertiesPanel>
</div>
@ -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}
></CellOptionsPopup>
)}
@ -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}
></CheckListPopup>
)}

View File

@ -67,6 +67,7 @@ export const EditCellOptionPopup = ({
const onDeleteOptionClick = async () => {
const svc = new SelectOptionCellBackendService(cellIdentifier);
await svc.deleteOption([editingSelectOption]);
onOutsideClick();
};
return (

View File

@ -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<void>;
}) => {
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<boolean[]>([]);
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 = ({
>
<TrashSvg></TrashSvg>
</i>
<Switch value={!hiddenProperties[cellIndex]} setValue={(v) => toggleHideProperty(v, cellIndex)}></Switch>
<Switch
value={!!databaseStore.fields[cell.cellIdentifier.fieldId]?.visible}
setValue={(v) => toggleHideProperty(v, cellIndex)}
></Switch>
</div>
</div>
))}
@ -108,17 +153,17 @@ export const PropertiesPanel = ({
<div className={'flex flex-col gap-2 text-xs'}>
{showBasicProperties && (
<div className={'flex flex-col'}>
{typesOrder.map((t, i) => (
{typesOrder.map((type, i) => (
<button
onClick={() => console.log('type clicked')}
onClick={() => addSelectedFieldType(type)}
key={i}
className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
>
<i className={'h-5 w-5'}>
<FieldTypeIcon fieldType={t}></FieldTypeIcon>
<FieldTypeIcon fieldType={type}></FieldTypeIcon>
</i>
<span>
<FieldTypeName fieldType={t}></FieldTypeName>
<FieldTypeName fieldType={type}></FieldTypeName>
</span>
</button>
))}

View File

@ -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<IDatabaseField> {
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,
};
}

View File

@ -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<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
const [cellController, setCellController] = useState<CellController<any, any>>();
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,
},
},
})
);
}
}
},
});

View File

@ -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) => {

View File

@ -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 {

View File

@ -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<RowInfo>();
@ -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}

View File

@ -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 = ({
</button>
<div className={'flex flex-col gap-3'}>
{cells
.filter((cell) => cell.fieldId !== groupByFieldId)
.filter(
(cell) => cell.fieldId !== groupByFieldId && databaseStore.fields[cell.cellIdentifier.fieldId].visible
)
.map((cell, cellIndex) => (
<BoardCell
key={cellIndex}

View File

@ -6,11 +6,11 @@ import { DatabaseController } from '$app/stores/effects/database/database_contro
import { Droppable } from 'react-beautiful-dnd';
import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
import { useTranslation } from 'react-i18next';
import { useEffect, useState } from 'react';
export const BoardGroup = ({
viewId,
controller,
allRows,
groupByFieldId,
onNewRowClick,
onOpenRow,
@ -18,7 +18,6 @@ export const BoardGroup = ({
}: {
viewId: string;
controller: DatabaseController;
allRows: readonly RowInfo[];
groupByFieldId: string;
onNewRowClick: () => void;
onOpenRow: (rowId: RowInfo) => void;
@ -26,6 +25,23 @@ export const BoardGroup = ({
}) => {
const { t } = useTranslation();
const [rows, setRows] = useState<RowInfo[]>([]);
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 (
<div className={'flex h-full w-[250px] flex-col rounded-lg bg-surface-1'}>
<div className={'flex items-center justify-between p-4'}>
@ -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 (
<BoardCard
viewId={viewId}
controller={controller}
@ -61,8 +76,6 @@ export const BoardGroup = ({
groupByFieldId={groupByFieldId}
onOpenRow={onOpenRow}
></BoardCard>
) : (
<span key={index}></span>
);
})}
</div>

View File

@ -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,
});

View File

@ -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:

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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 = () => {

View File

@ -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';

View File

@ -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);

View File

@ -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) {

View File

@ -1,4 +1,4 @@
import { useAppSelector } from '../../stores/store';
import { useAppSelector } from '$app/stores/store';
export const WorkspaceUser = () => {
const currentUser = useAppSelector((state) => state.currentUser);

View File

@ -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;

View File

@ -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<RepeatedViewPB, FlowyError>) => void;
import { FolderNotification } from '@/services/backend';
import { ChangeNotifier } from '$app/utils/change_notifier';
import { FolderNotificationObserver } from '../notifications/observer';
export class AppObserver {
_appNotifier = new ChangeNotifier<Result<RepeatedViewPB, FlowyError>>();
_viewsNotifier = new ChangeNotifier<void>();
_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();
};
}

View File

@ -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;

View File

@ -14,8 +14,8 @@ export const pagesSlice = createSlice({
name: 'pages',
initialState: initialState,
reducers: {
didReceivePages(state, action: PayloadAction<IPage[]>) {
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<IPage>) {
state.push(action.payload);